因为我们最近的一个项目数据库访问呢层使用Ibatis, 今天团队成员问Ibatis中只写接口,不写实现,Ibatis是如何帮助我们查询数据。其实原理很简单,就是Java的反射和代理,因为Java的代理是真对于接口的。所以我们就可以在开发中DAO模块就直接写接口和对用的SQL就可以。实现类由我们生成代理,当代理方法被调用的时候我们就使用通用的数据库访问方法去接管它,为清晰简单的说明原理,这里直接给代码。
public class User {
private long id;
private String name;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Query {
String value();
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Update {
String value();
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Insert {
String value();
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Param {
String value();
}
public interface UserDao {
@Update("delete from t_user where id=:id")
void delete(@Param("id")long id);
@Insert("insert t_user(id,name) values(:id,:name)")
long add(@Param("id")long id,@Param("name")String name);
@Update("update t_user set name=:name where id=:id")
boolean modify(@Param("name")String name,@Param("id") long id);
@Query("Select * from t_user id=:id")
User find(@Param("id")long id);
}
关键代码1
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author Silence
*/
public class DaoInvocationHandlerImpl implements InvocationHandler {
private static Connection conn = null;
static{
try {
conn = DriverManager.getConnection("jdbc:mysql://localhost/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true", "test", "test");
} catch (SQLException ex) {
Logger.getLogger(DaoInvocationHandlerImpl.class.getName()).log(Level.SEVERE, null, ex);
}
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Logger.getLogger(DaoInvocationHandlerImpl.class.getName()).log(Level.SEVERE, "{0} invoke", method.getName());
Annotation[][] annos = method.getParameterAnnotations();
try {
if (method.getAnnotation(Query.class) != null) {
Query query = method.getAnnotation(Query.class);
NamedParameterStatement nps = new NamedParameterStatement(conn, query.value());
for(int i=0;i<annos.length;i++){
for(Annotation an:annos[i]){
if(an instanceof Param){
nps.setObject(((Param)an).value(), args[i]);
}
}
}
//这里也可以再次包装
return nps.executeQuery();
} else if (method.getAnnotation(Insert.class) != null) {
//代码省略
return null;
} else if (method.getAnnotation(Update.class) != null){
//代码省略
return null;
} else {
//代码省略
return null;
}
} catch (Exception e) {
Logger.getLogger(DaoInvocationHandlerImpl.class.getName()).log(Level.SEVERE, "error occurs in [" + method.toGenericString() + "]", e);
throw e;
}
}
}
//这段代码来自网上的主要用于命名参数,就是把sql中的参数替换成问号,并设置对应的值
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
*
* @author Silence
*/
public class NamedParameterStatement {
/** The statement this object is wrapping. */
private final PreparedStatement statement;
/** Maps parameter names to arrays of ints which are the parameter indices.
*/
private final Map indexMap;
/**
* Creates a NamedParameterStatement. Wraps a call to
* c.{@link Connection#prepareStatement(java.lang.String)
prepareStatement}.
* @param connection the database connection
* @param query the parameterized query
* @throws SQLException if the statement could not be created
*/
public NamedParameterStatement(Connection connection, String query) throws SQLException {
indexMap=new HashMap();
String parsedQuery=parse(query, indexMap);
statement=connection.prepareStatement(parsedQuery);
}
/**
* Parses a query with named parameters. The parameter-index mappings are
put into the map, and the
* parsed query is returned. DO NOT CALL FROM CLIENT CODE. This
method is non-private so JUnit code can
* test it.
* @param query query to parse
* @param paramMap map to hold parameter-index mappings
* @return the parsed query
*/
static final String parse(String query, Map paramMap) {
// I was originally using regular expressions, but they didn't work well for ignoring
// parameter-like strings inside quotes.
int length=query.length();
StringBuffer parsedQuery=new StringBuffer(length);
boolean inSingleQuote=false;
boolean inDoubleQuote=false;
int index=1;
for(int i=0;i<length;i++) {
char c=query.charAt(i);
if(inSingleQuote) {
if(c=='\'') {
inSingleQuote=false;
}
} else if(inDoubleQuote) {
if(c=='"') {
inDoubleQuote=false;
}
} else {
if(c=='\'') {
inSingleQuote=true;
} else if(c=='"') {
inDoubleQuote=true;
} else if(c==':' && i+1<length &&
Character.isJavaIdentifierStart(query.charAt(i+1))) {
int j=i+2;
while(j<length && Character.isJavaIdentifierPart(query.charAt(j))) {
j++;
}
String name=query.substring(i+1,j);
c='?'; // replace the parameter with a question mark
i+=name.length(); // skip past the end if the parameter
List indexList=(List)paramMap.get(name);
if(indexList==null) {
indexList=new LinkedList();
paramMap.put(name, indexList);
}
indexList.add(new Integer(index));
index++;
}
}
parsedQuery.append(c);
}
// replace the lists of Integer objects with arrays of ints
for(Iterator itr=paramMap.entrySet().iterator(); itr.hasNext();) {
Map.Entry entry=(Map.Entry)itr.next();
List list=(List)entry.getValue();
int[] indexes=new int[list.size()];
int i=0;
for(Iterator itr2=list.iterator(); itr2.hasNext();) {
Integer x=(Integer)itr2.next();
indexes[i++]=x.intValue();
}
entry.setValue(indexes);
}
return parsedQuery.toString();
}
/**
* Returns the indexes for a parameter.
* @param name parameter name
* @return parameter indexes
* @throws IllegalArgumentException if the parameter does not exist
*/
private int[] getIndexes(String name) {
int[] indexes=(int[])indexMap.get(name);
if(indexes==null) {
throw new IllegalArgumentException("Parameter not found: "+name);
}
return indexes;
}
/**
* Sets a parameter.
* @param name parameter name
* @param value parameter value
* @throws SQLException if an error occurred
* @throws IllegalArgumentException if the parameter does not exist
* @see PreparedStatement#setObject(int, java.lang.Object)
*/
public void setObject(String name, Object value) throws SQLException {
int[] indexes=getIndexes(name);
for(int i=0; i < indexes.length; i++) {
statement.setObject(indexes[i], value);
}
}
/**
* Sets a parameter.
* @param name parameter name
* @param value parameter value
* @throws SQLException if an error occurred
* @throws IllegalArgumentException if the parameter does not exist
* @see PreparedStatement#setString(int, java.lang.String)
*/
public void setString(String name, String value) throws SQLException {
int[] indexes=getIndexes(name);
for(int i=0; i < indexes.length; i++) {
statement.setString(indexes[i], value);
}
}
/**
* Sets a parameter.
* @param name parameter name
* @param value parameter value
* @throws SQLException if an error occurred
* @throws IllegalArgumentException if the parameter does not exist
* @see PreparedStatement#setInt(int, int)
*/
public void setInt(String name, int value) throws SQLException {
int[] indexes=getIndexes(name);
for(int i=0; i < indexes.length; i++) {
statement.setInt(indexes[i], value);
}
}
/**
* Sets a parameter.
* @param name parameter name
* @param value parameter value
* @throws SQLException if an error occurred
* @throws IllegalArgumentException if the parameter does not exist
* @see PreparedStatement#setInt(int, int)
*/
public void setLong(String name, long value) throws SQLException {
int[] indexes=getIndexes(name);
for(int i=0; i < indexes.length; i++) {
statement.setLong(indexes[i], value);
}
}
/**
* Sets a parameter.
* @param name parameter name
* @param value parameter value
* @throws SQLException if an error occurred
* @throws IllegalArgumentException if the parameter does not exist
* @see PreparedStatement#setTimestamp(int, java.sql.Timestamp)
*/
public void setTimestamp(String name, Timestamp value) throws SQLException
{
int[] indexes=getIndexes(name);
for(int i=0; i < indexes.length; i++) {
statement.setTimestamp(indexes[i], value);
}
}
/**
* Returns the underlying statement.
* @return the statement
*/
public PreparedStatement getStatement() {
return statement;
}
/**
* Executes the statement.
* @return true if the first result is a {@link ResultSet}
* @throws SQLException if an error occurred
* @see PreparedStatement#execute()
*/
public boolean execute() throws SQLException {
return statement.execute();
}
/**
* Executes the statement, which must be a query.
* @return the query results
* @throws SQLException if an error occurred
* @see PreparedStatement#executeQuery()
*/
public ResultSet executeQuery() throws SQLException {
return statement.executeQuery();
}
/**
* Executes the statement, which must be an SQL INSERT, UPDATE or DELETE
statement;
* or an SQL statement that returns nothing, such as a DDL statement.
* @return number of rows affected
* @throws SQLException if an error occurred
* @see PreparedStatement#executeUpdate()
*/
public int executeUpdate() throws SQLException {
return statement.executeUpdate();
}
/**
* Closes the statement.
* @throws SQLException if an error occurred
* @see Statement#close()
*/
public void close() throws SQLException {
statement.close();
}
/**
* Adds the current set of parameters as a batch entry.
* @throws SQLException if something went wrong
*/
public void addBatch() throws SQLException {
statement.addBatch();
}
/**
* Executes all of the batched statements.
*
* See {@link Statement#executeBatch()} for details.
* @return update counts for each statement
* @throws SQLException if something went wrong
*/
public int[] executeBatch() throws SQLException {
return statement.executeBatch();
}
}
关键代码2
public class Test {
public static void main(String[] args) {
//如果使用spring,可以实现spring工厂bean接口包装这段代码,为server类提供注入
UserDao dao = (UserDao) Proxy.newProxyInstance(
DaoInvocationHandlerImpl.class.getClassLoader(),
new Class[]{UserDao.class},
new DaoInvocationHandlerImpl());
dao.find(1L);
}
}
以上代码比较粗糙,只用于技术实现验证使用;我没有测试上面代码,感兴趣的农码可以试试,O(∩_∩)O哈哈~