首先科普一下BaseDAO:
BaseDAO一般是提供从数据库 增加、删除、修改记录、查询所有记录、查询符合某个条件记录、取得某条记录等方法的底层数据操作自定义类。
由于我们可能操作多个数据库表,这样就需要为每个表提供一个操作他的类 xxDAO, 这些DAO继承BaseDAO 就可以省略很多重复代码(从数据库 增加、删除、修改记录、查询所有记录、查询符合某个条件记录、取得某条记录等方法的代码)。
最初,我们的BaseDAO是这样子的:
列名注解:用于表示 JavaBean中的属性所对应的列名称
此时,我们的JavaBean需要使用注解
我们需要修改BaseDAO,让他使用可配置的注解来执行增删改查操作,这里就显得有些麻烦
为了方便演示,把一些通用操作全部写到了add()方法中,实际使用时这些方法只需执行一次
此时终于完成了对于任何JavaBean都可以操作
的add()方法,扩展性强,十分灵活,可配置,BaseDAO与JavaB
ean解耦,表名、列名与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
代码无法重用,质量低下
。。。
我们想要实现的是,一个
Base
DAO,可以对任何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方法。
太晚了,睡觉。