首先科普一下BaseDAO:
BaseDAO一般是提供从数据库 增加、删除、修改记录、查询所有记录、查询符合某个条件记录、取得某条记录等方法的底层数据操作自定义类。
由于我们可能操作多个数据库表,这样就需要为每个表提供一个操作他的类 xxDAO, 这些DAO继承BaseDAO 就可以省略很多重复代码(从数据库 增加、删除、修改记录、查询所有记录、查询符合某个条件记录、取得某条记录等方法的代码)。
最初,我们的BaseDAO是这样子的:
列名注解:用于表示JavaBean中的属性所对应的列名称
此时,我们的JavaBean需要使用注解
我们需要修改BaseDAO,让他使用可配置的注解来执行增删改查操作,这里就显得有些麻烦
为了方便演示,把一些通用操作全部写到了add()方法中,实际使用时这些方法只需执行一次
此时终于完成了对于任何JavaBean都可以操作的add()方法,扩展性强,十分灵活,可配置,BaseDAO与JavaBean解耦,表名、列名与JavaBean解耦。
搞了这么多,还仅仅是一个add方法。
太晚了,睡觉。
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;
}
实现类:
此时我们可以使用properties或者XML配置文件来解决这个问题。
那么,问题又来了,如何使用我们今天刚学习的注解来替代配置文件呢?
首先,我们需要来三个自定义注解
表名注解:用于指示JavaBean所处表名
主键注解:用于表示JavaBean中的哪个属性是主键
(为使代码更加清晰明了,本文所有DAO的实现类均只实现add()方法)
现在我们假设:如果表名和JavaBean的类名相同,参数个数和JavaBean的属性个数相同,参数名称和JavaBean的属性名称相同
那么,此时我们能够实现嘛?
解:
我们可以写一个自己的DAO继承BaseDAO,在这个子类声明中明确泛型的具体类型(JavaBean,User)
我们就需要用到反射和泛型:
改良后的DAO接口:
虽然达成需求,完成了BaseDAO与JavaBean解耦,但显然这还是不够完美,扩展性和灵活性仍然受到限制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);
}
}
现在存在三个问题,
- 表名不确定
- 参数个数不确定
- 参数名称是什么不知道
现在我们假设:如果表名和JavaBean的类名相同,参数个数和JavaBean的属性个数相同,参数名称和JavaBean的属性名称相同
那么,此时我们能够实现嘛?
解:
public class UserDAO extends BaseDAO<User>{}
然后可以使用如下方法调用链
Class c = (Class) ((ParameterizedType)(this.getClass().getGenericSuperclass())).getActualTypeArguments()[0];
得到子类明确的泛型类型,这个类型即为JavaBean的类型User
表名即为:c.getSimpleName() //User
此时可以解决第一个问题,表名不确定的问题,我们可以使JavaBean的类型与数据库表名相同,此时就可以获取JavaBean类名作为表名
然后我们可以通过反射,得到JavaBean的所有属性,这些属性作为参数,使JavaBean的属性名和数据库表内的列名称相同。
如此一来,便解决来了第二、三个问题 我们就需要用到反射和泛型:
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;
}
改良后的实现类:
此时我们完成了上述目标“一个DAO,可以对任何JavaBean增删改查”
但是别忘了,所有这一切,都建立在我们的三个条件都成立的情况下:
1、表名和JavaBean的类名相同
2、参数个数和JavaBean的属性个数相同
3、参数名称和JavaBean的属性名称相同
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 {
//1、构造SQL添加记录语句
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(?,?,?)
//2、将实际参数按顺序存入Object数组
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();
}
//3、执行sql语句
qr.update(DBUtil.getConnection(), sql, objects);
}
}
但是别忘了,所有这一切,都建立在我们的三个条件都成立的情况下:
1、表名和JavaBean的类名相同
2、参数个数和JavaBean的属性个数相同
3、参数名称和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 {
//1、构造SQL添加记录语句
//获取表名
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(?,?,?)
//2、将实际参数按顺序存入Object数组
//获取主键名和列名
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);
}
//3、执行sql语句
qr.update(DBUtil.getConnection(), sql, objects);
}
}
搞了这么多,还仅仅是一个add方法。
太晚了,睡觉。

542

被折叠的 条评论
为什么被折叠?



