万能的BaseDAO和注解的应用

首先科普一下BaseDAO:
BaseDAO一般是提供从数据库 增加、删除、修改记录、查询所有记录、查询符合某个条件记录、取得某条记录等方法的底层数据操作自定义类。
由于我们可能操作多个数据库表,这样就需要为每个表提供一个操作他的类 xxDAO, 这些DAO继承BaseDAO 就可以省略很多重复代码(从数据库 增加、删除、修改记录、查询所有记录、查询符合某个条件记录、取得某条记录等方法的代码)。

最初,我们的BaseDAO是这样子的:
 接口:
package com.kxrjkf.user.dao;
import java.sql.SQLException;
import com.kxrjkf.user.domain.User;
public interface BaseDAO {
    void add(User user)throws SQLException;
    
    void update(User user)throws SQLException;
    
    void delete(User user)throws SQLException;
    
    User find(int id)throws SQLException;
}
 实现类:
(为使代码更加清晰明了,本文所有DAO的实现类均只实现add()方法) 
 
public class BaseDAOImpl implements BaseDAO {
    private QueryRunner qr = new QueryRunner();
    public void add(User user) throws SQLException {
        //sql
        String sql = "insert into user values(?,?,?)";
        //
        Object[] objects = new Object[] { user.getId(),user.getUsername(),user.getPassword()};
        //
        qr.update(DBUtil.getConnection(), sql, objects);
    }
}
BaseDAO的接口和其实现类均和JavaBean的User类紧密耦合,不利于程序扩展性,BaseDAO代码无法重用,质量低下。。。
我们想要实现的是,一个BaseDAO,可以对任何JavaBean增删改查,如下所示:

public class BaseDAO<T> {
 
    QueryRunner qr = new QueryRunner();
    public void add(T bean) throws SQLException {
        String sql = "insert into  values(?)";
        Object[] params={/**/};
        qr.update(sql, params);
    }
}
现在存在三个问题,
  1. 表名不确定
  2. 参数个数不确定
  3. 参数名称是什么不知道

现在我们假设:如果表名和JavaBean的类名相同,参数个数和JavaBean的属性个数相同,参数名称和JavaBean的属性名称相同

那么,此时我们能够实现嘛?
 

解:
我们可以写一个自己的DAO继承BaseDAO,在这个子类声明中明确泛型的具体类型(JavaBean,User) 
public class UserDAO extends BaseDAO<User>{}
然后可以使用如下方法调用链
Class c = (Class) ((ParameterizedType)(this.getClass().getGenericSuperclass())).getActualTypeArguments()[0];
得到子类明确的泛型类型,这个类型即为JavaBean的类型User
表名即为:c.getSimpleName()        //User
此时可以解决第一个问题,表名不确定的问题,我们可以使JavaBean的类型与数据库表名相同,此时就可以获取JavaBean类名作为表名
然后我们可以通过反射,得到JavaBean的所有属性,这些属性作为参数,使JavaBean的属性名和数据库表内的列名称相同。
如此一来,便解决来了第二、三个问题 

我们就需要用到反射和泛型:
 改良后的DAO接口:
package com.kxrjkf.user.daoex;
import java.sql.SQLException;
import com.kxrjkf.user.domain.User;
public interface BaseDAO<T> {
    void add(T bean)throws SQLException;
    
    void update(T bean)throws SQLException;
    
    void delete(T bean)throws SQLException;
    
    T find(int id)throws SQLException;
}
 改良后的实现类:
 
package com.kxrjkf.user.daoex.impl;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.sql.SQLException;
import org.apache.commons.dbutils.QueryRunner;
import com.kxrjkf.user.daoex.BaseDAO;
import com.kxrjkf.user.util.DBUtil;
public class BaseDAOImpl<T> implements BaseDAO<T> {
    //
    private Class clazz = (Class) ((ParameterizedType)(this.getClass().getGenericSuperclass())).getActualTypeArguments()[0];
    private QueryRunner qr = new QueryRunner();
    public void add(T bean) throws SQLException {
        //1SQL
        String sql = "insert into "+ clazz.getSimpleName() +" values(";
        //JavaBean
        int sum = clazz.getDeclaredFields().length;
        //
        for (int i = sum; i > 0; i--) {
            sql+="?";
            if (i!=1) {
                sql+=",";
            }
        }
        sql+=")";   //insert into user values(?,?,?)
        //2Object
        Object[] objects =null;
        try {
            //JavaBean
            Field[] fields = clazz.getDeclaredFields();
            //Object
            objects = new Object[fields.length];
            //Object
            for (int i = 0; i < objects.length; i++) {
                //访使
                fields[i].setAccessible(true);
                //Object
                objects[i] = fields[i].get(bean);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        //3sql
        qr.update(DBUtil.getConnection(), sql, objects);
    }
}
 
此时我们完成了上述目标“一个DAO,可以对任何JavaBean增删改查
但是别忘了,所有这一切,都建立在我们的三个条件都成立的情况下:
1、表名和JavaBean的类名相同
2、参数个数和JavaBean的属性个数相同
3、参数名称和JavaBean的属性名称相同

虽然达成需求,完成了BaseDAO与JavaBean解耦,但显然这还是不够完美,扩展性和灵活性仍然受到限制
此时我们可以使用properties或者XML配置文件来解决这个问题。
那么,问题又来了,如何使用我们今天刚学习的注解来替代配置文件呢?

 首先,我们需要来三个自定义注解

表名注解:用于指示JavaBean所处表名 
 
package com.kxrjkf.user.domain;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)    //
@Target(ElementType.TYPE)   //
public @interface Table {
    String value();
}
 
主键注解:用于表示JavaBean中的哪个属性是主键
 
package com.kxrjkf.user.domain;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ID {
    String value();
}
 
列名注解:用于表示JavaBean中的属性所对应的列名称
 
package com.kxrjkf.user.domain;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
    String value();
}
 
此时,我们的JavaBean需要使用注解
 
package com.kxrjkf.user.domain;
@Table("user")    //
public class User {
    @ID("id")   //
    private int id;
    @Column("username")
    private String username;
    @Column("password")
    private String password;
}
 
我们需要修改BaseDAO,让他使用可配置的注解来执行增删改查操作,这里就显得有些麻烦
 
package com.kxrjkf.user.daoex.impl;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.sql.SQLException;
import org.apache.commons.dbutils.QueryRunner;
import com.kxrjkf.user.daoex.BaseDAO;
import com.kxrjkf.user.domain.Column;
import com.kxrjkf.user.domain.ID;
import com.kxrjkf.user.domain.Table;
import com.kxrjkf.user.util.DBUtil;
public class BaseDAOImplEXS<T> implements BaseDAO<T> {
    private Class clazz = (Class) ((ParameterizedType)(this.getClass().getGenericSuperclass())).getActualTypeArguments()[0];
    private QueryRunner qr = new QueryRunner();
    public void add(T bean) throws SQLException {
        //1SQL
        
        //
        Table table = (Table) clazz.getAnnotation(Table.class);
        String tableName = table.value();
        
        String sql = "insert into "+ tableName +" values(";
        int sum = clazz.getDeclaredFields().length;
        for (int i = sum; i > 0; i--) {
            sql+="?";
            if (i!=1) {
                sql+=",";
            }
        }
        sql+=")";   //insert into user values(?,?,?)
        //2Object
        
        //
        Field[] fields = clazz.getDeclaredFields();
        int zong=fields.length;     //
        String zhujian = null;      //
        String[] lie = new String[zong];    //
        
        for (int i = 0; i < lie.length; i++) {
            try {
                ID id = fields[i].getAnnotation(ID.class);  //
                zhujian = id.value();   //
                continue;   //
            } catch (NullPointerException e) {
                //
            }
            try {
                Column column = fields[i].getAnnotation(Column.class);  //
                lie[i] = column.value();
            } catch (NullPointerException e) {
                //
            }
        }
        System.out.println(zong);
        Object[] objects =null;
        try {
            objects = new Object[zong];
            for (int i = 0; i < zong; i++) {
                fields[i].setAccessible(true);
                objects[i] = fields[i].get(bean);
/*              System.out.println(fields[i].getName()+":"+objects[i]);
 *              
                id:3
                username:zhangsan
                password:123456*/
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        for (Object object : objects) {
            System.out.println(object);
        }
        //3sql
        qr.update(DBUtil.getConnection(), sql, objects);
    }
}
 
 为了方便演示,把一些通用操作全部写到了add()方法中,实际使用时这些方法只需执行一次
 
此时终于完成了对于任何JavaBean都可以操作的add()方法,扩展性强,十分灵活,可配置,BaseDAO与JavaBean解耦,表名、列名与JavaBean解耦

搞了这么多,还仅仅是一个add方法。
太晚了,睡觉。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值