本文来自 fair-jm.iteye.com 转截请注明出处
用了下JPA对于其方便的操作很好奇 通过继承CRUDRepository等接口 DAO不用写实现类就可以在注入后实现基本的增删改查功能
搜索到了一些内容:
http://my.oschina.net/xdev/blog/126049 这里有一些实现的原理 比较详细
http://sunting-bcwl.iteye.com/blog/768989 代码实现如何获得泛型等内容
实现很简单 用了JPA和java的动态代理以及反射
基本的结构
有一个CRUD的接口:
package jdbc;
import java.util.List;
public interface CRUD {
public List query();
public T findOne(I id);
public void delete(I id);
public void add(T t);
public int update(T t);
}
此接口是所有DAO的接口必须继承的
实践中用了两个DAO user和item
这里以User为例
User的代码如下:
package com.cc.proxy.test.domain;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name="user")
public class User {
@Id
private int uid;
private String name;
private int sex;
private String description;
private int age;
//省略getter和setter
}
UserDao:
package com.cc.proxy.test.dao;
import com.cc.proxy.test.domain.User;
import jdbc.CRUD;
public interface UserDao extends CRUD {
}
通过代理User就可以使用CRUD等方法(我实际上就写了query和add 其他方法在代理的invoke中补充就可以了):
=================================================================================
=================================================================================
接下去是最主要的代理类的代码:
代理类ProxyDao(实现InvocationHandler)的属性如下:
private String tableName=null; //表名
private Class entity=null; //实体类
private Class idType=null; //id类型
private String id=null; //id在实体类中的属性名(方便操作)
//filed<--->column
private Map columMapping=new HashMap<>();
构造方法:
public ProxyDao(Class dao) throws Exception{
if(!check(dao)){
throw new Exception("not a dao");
}
}
其中check方法是完成DAO的检查
这里的检查主要包括:
- DAO是否继承了CRUD接口
- CRUD接口上的泛型是否是实体(有无@Entity和@Id)
代码如下:
private boolean check(Class dao) throws Exception{
boolean hasCRUD=false;
//检查是否继承了CRUD接口 没有的话就返回false
for(Class inter:dao.getInterfaces()){
if(inter==CRUD.class){
hasCRUD=true;
break;
}
}
if(!hasCRUD){
return false;
}
//取得第一个接口的信息 这里也就是CRUD接口
Type t=dao.getGenericInterfaces()[0];
//获得CRUD的泛型 第一个是对应的实体类 第二个是对应的id类型
entity=(Class)((ParameterizedType)t).getActualTypeArguments()[0];
idType=(Class)((ParameterizedType)t).getActualTypeArguments()[1];
Annotation[] as=entity.getAnnotations();
//检查是否有Entity这个annotation没有则退出
boolean hasEntity=false;
for(Annotation a:as){
if(a.annotationType()==javax.persistence.Entity.class){
hasEntity=true;
}
if(a.annotationType()==javax.persistence.Table.class){
tableName=((javax.persistence.Table)a).name();
}
}
if(!hasEntity){
return false;
}
//如果table的name没有值 那么用实体类的类名就可以了
if(tableName==null){
tableName=entity.getSimpleName();
}
boolean hasId=false;
Field[] fs=entity.getDeclaredFields();
for(Field f:fs){
if(f.getAnnotation(Id.class)!=null){
id=f.getName();
hasId=true;
}
//这里建立 属性<-->字段名的映射
if(f.getAnnotation(Column.class)!=null){
columMapping.put(f.getName(), f.getAnnotation(Column.class).name());
}else{
columMapping.put(f.getName(),f.getName());
}
}
if(!hasId){
return false;
}
return true;
}
最后就是invoke方法了,invoke完成运行方法的指派:
public static final String QUERY="select * from %s ";
public static final String ADD="insert into %s(%s) values (%s)";
public static final String DELETE="delete from %s where %s=?";
public static final String FIND="select * from %s where %s=?";
public static final String UPDATE="update %s set %s where %s=?";
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if(method.getName().equals("query")){
List list=new ArrayList<>();
try(Connection conn=DBConnection.getConnection()){
PreparedStatement pst=conn.prepareStatement(String.format(QUERY, tableName));
ResultSet rs=pst.executeQuery();
while(rs.next()){
Object o=entity.newInstance();
for(Field f:entity.getDeclaredFields()){
f.setAccessible(true);
f.set(o, rs.getObject(columMapping.get(f.getName()),f.getType()));
}
list.add(o);
}
}
return list;
}
if(method.getName().equals("add")){
Object o=args[0];
StringBuffer arg=new StringBuffer();
StringBuffer values=new StringBuffer();
for(Field f:entity.getDeclaredFields()){
if(f.getName().equals(id)){ //id不用自己插入
continue;
}
f.setAccessible(true);
if(f.get(o)!=null){
arg.append(columMapping.get(f.getName())).append(",");
values.append(String.format("'%s'", f.get(o))).append(",");
}
}
// arg.substring(0,arg.length()-1);
// values.substring(0,values.length()-1);
try(Connection conn=DBConnection.getConnection()){
PreparedStatement pst=conn.prepareStatement(String.format(ADD, tableName,arg.substring(0,arg.length()-1),values.substring(0,values.length()-1)));
pst.execute();
}
}
if(method.getName().equals("delete")){
Object o=args[0];
try(Connection conn=DBConnection.getConnection()){
PreparedStatement pst=conn.prepareStatement(String.format(DELETE, tableName,id));
pst.setObject(1, o);
pst.execute();
}
}
if(method.getName().equals("findOne")){
Object o=args[0];
try(Connection conn=DBConnection.getConnection()){
PreparedStatement pst=conn.prepareStatement(String.format(FIND, tableName,id));
pst.setObject(1, o);
ResultSet rs=pst.executeQuery();
if(rs.next()){
Object ob=entity.newInstance();
for(Field f:entity.getDeclaredFields()){
f.setAccessible(true);
f.set(ob, rs.getObject(columMapping.get(f.getName()),f.getType()));
}
return ob;
}else{
return null;
}
}
}
if(method.getName().equals("update")){
Object o=args[0];
Object idNumber=null;
StringBuffer set=new StringBuffer();
for(Field f:entity.getDeclaredFields()){
f.setAccessible(true);
if(f.getName().equals(id)){
idNumber=f.get(o);
continue;
}
if(f.get(o)!=null){
set.append(columMapping.get(f.getName())+"="+String.format("'%s'", f.get(o))).append(",");
}
}
if(idNumber==null){
return 0;
}
try(Connection conn=DBConnection.getConnection()){
PreparedStatement pst=conn.prepareStatement(String.format(UPDATE, tableName,set.substring(0,set.length()-1),id));
pst.setObject(1, idNumber);
return pst.executeUpdate();
}
}
return null;
}
不只是这些方法 对于UserDao如果有findOneByName这种方法 完全可以通过invoke方法进行分析和执行相应动作(这里可以用一个判断方法名是findOneBy开头的来 后面的属性放入SQL中就可以了)
代码中偷懒直接对属性用Field来取 实际中应该通过getter和setter来取和赋值 自己实验一下就怎么方便怎么写了
实现起来不是很难 就是一些基本的反射的操作和一些基础的逻辑判断
从前也未思考过动态代理有什么用(最多就是一些AOP的内容) 对于直接用来实现接口 压根就没想过..
写代码这种东西 总是能找到新的乐趣啊....
此外关于动态代理的其他的用法可以看:
http://www.infoq.com/cn/articles/cf-java-reflection-dynamic-proxy
infoQ里的内容挺不错的~~~
2013最后一篇博客了~~来年也要玩得开心^_^~~