Dao层接口实现优化(针对CRUD)
如果一个项目中有多个实体类(domian包下面有多个类,例如学生,老师,用户,部门等),那么对应的dao下面也会有多个对应的接口和多个实现类。考虑如何优化抽取dao层对应的接口和实现类。
Dao
DAO(Data Access Object)是一个数据访问接口,数据访问:顾名思义就是与数据库打交道。夹在业务逻辑与数据库资源中间。
在核心J2EE模式中是这样介绍DAO模式的:为了建立一个健壮的J2EE应用,应该将所有对数据源的访问操作抽象封装在一个公共API中。用程序设计的语言来说,就是建立一个接口,接口中定义了此应用程序中将会用到的所有事务方法。在这个应用程序中,当需要和数据源进行交互的时候则使用这个接口,并且编写一个单独的类来实现这个接口在逻辑上对应这个特定的数据存储。
那么在实际开发中就一定会存在多个实体类,每个实体类都会有操作,如果为每一个实体类分别创建实现,可以,但是会有比较大的冗余,所以这时便需要对整个dao层结构进行重构优化。
思路:
每个实体类,例如老师、学生…等等,都会存在CRUD操作,我们可以创建一个BaseDao将这些操作抽取出来,创建对应的实现类。再创建一个工具类提供连接数据库、释放连接操作。
期望:
能够简化SQL操作,不用每次操作数据库都需要输入繁杂的sql语句,可以实现传入一个对象即可以将对象存入到数据库
具体实现CRUD
- 首先需要创建工具类提供连接、释放连接
- 其次定义基类Dao,定义CRUD方法
- 创建实现类实现基类Dao接口
大体思路:
1)通过定义泛型方法使得能够接受所有类型,并且保证传入类型在方法整个使用过程中始终为传入类型。
2)通过获取传入对象字节码对象获取到该对象的所有字段以拼接成为SQL语句中的列值,形如 Insert into Student(字段1,字段2,…)。字段拼接则是实现括号内的所有内容的拼接。接下来我们还需要拼接出SQL后半部分的语句,这里采用的是PreparedStatement来实现SQL的执行。preparedStatement将会在本文末尾详细介绍。
使用PreparedStatement的好处在于不用拼接值,安全性、效率高于Statement
所以,后半句需要拼接对应字段个数的占位符。再为占位符赋值便可以实现SQL语句执行 - 进行CRUD操作
建立工具类
- 首先需要建立资源文件夹,里面配置好数据库连接信息
- 使用连接池获取连接
最重要的是需要导包。需要的包有:commons-dbcp-1.4.jar 用于连接池操作, commons-pool-1.5.6.jar连接池包, mysql-connector-java-5.1.26-bin.jar用于连接数据库
代码:
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
/**
* 获取连接类
* @author xer
*
*/
public enum JDBCUtil {
/**
* 连接池
* 不直接通过DriverManager获取连接,而是通过在一个“池子”中创建多个连接,
* 每次都到池子里去取出空闲连接,进行操作,操作完成之后释放连接,放回连接池
*
* 采取方式二连接池操作
*/
instance;
//要在静态代码块中使用properties对象,所以需要static修饰,但是不希望外部能用到此对象所以private修饰
private static Properties pro = new Properties();
private static DataSource bds = new BasicDataSource();
static {
try {
//通过当前线程获取到类加载器将数据以流的形式读入
//获取连接池方式1
//读取资源文件中数据库配置信息,注意采用方式二时资源文件中的内容名称要规范定义
pro.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("db.properties"));
/*bds.setDriverClassName(pro.getProperty("driverClassName"));
bds.setUrl(pro.getProperty("url"));
bds.setUsername(pro.getProperty("username"));
bds.setPassword(pro.getProperty("password"));*/
//获取连接池方式2
bds = BasicDataSourceFactory.createDataSource(pro);
} catch (Exception e) {
e.printStackTrace();
}
}
public Connection getConnection() {
try {
return bds.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public void closeConnection(Connection conn,ResultSet rs,Statement cs) {
try {
if(rs != null) rs.close();
} catch (SQLException e1) {
e1.printStackTrace();
}finally {
try {
if(cs != null)cs.close();
} catch (SQLException e) {
e.printStackTrace();
}finally {
try {
if(conn != null)conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
基类Dao
import java.util.List;
/**
* 各种实体类dao接口的父类接口
* 因为每一种实体类dao的操作无非就是CRUD,所以将这些抽取出来提供一个泛型父类接口
* 子类dao接口便可以继承实现baseDao指定类型的操作
* @author xer
*
* @param <T> 指定dao的类型
*/
public interface BaseDao<T> {
void add(T obj);
int del(T obj);
void update(T obj);
T query(T obj);
List<T> queryAll(T obj);
}
定义实现类
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import cn.itsource.dao.BaseDao;
import cn.itsource.util.JDBCUtil;
public class DaoImpl<T> implements BaseDao<T>{
/**
* 添加
* 添加数据
* 1.首先方法为泛型方法,传入对象为什么类型则T就是什么类型
* 2.通过Class获取到该对象字节码对象方便后面获取该对象所有字段做准备
* 3.通过拼接方法将获取到的所有字段拼接成SQL语句,并且拼接相应个数的问号
* 形如:INSERT INTO table(field1,field2.....)
* VALUES(?,?,?...)
* 4.使用preparedStatement为相应问号赋值
* 5.执行SQL得到结果返回,关闭连接
*
*/
@Override
public void add(T obj) {
PreparedStatement ps = null;
Connection conn = null;
try {
//获取传入对象的字节码对象
Class<? extends Object> clazz = obj.getClass();
//获得所有字段,因为我们使用的实体类多半都是private修饰的所以要使用getDeclaredFields
Field[] fields = clazz.getDeclaredFields();
//字段,用于拼接SQL语句使用
StringBuffer field = new StringBuffer();
//问号,用于拼接SQL
StringBuffer sb3 = new StringBuffer();
for (int i = 1; i< fields.length; i++) {
//因为要涉及到对私有字段的进行操作,所以需要设置权限
fields[i].setAccessible(true);
//拼接字符串
if(i != fields.length-1) {
field = field.append(fields[i].getName()+",");
}else {
field = field.append(fields[i].getName());
}
}
//拼接问号
for (int i = 1; i < fields.length; i++) {
if(i != fields.length-1) {
sb3 = sb3.append("?,");
}else {
sb3 = sb3.append("?");
}
}
String sql = "insert into "+clazz.getSimpleName()+"("+field+") values("+sb3+")";
System.out.println(sql);
//获取连接
conn = JDBCUtil.instance.getConnection();
//预编译SQL
ps = conn.prepareStatement(sql);
for (int i = 1; i < fields.length; i++) {
ps.setObject(i, fields[i].get(obj));
}
ps.executeUpdate();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally {
//关闭释放连接
JDBCUtil.instance.closeConnection(conn, null, ps);
}
}
/**
* 删除
* 思路:
* 删除之前先对传入对象进行查询,查询根据id
* 如果不存在则直接返回0
* 如果存在则继续进行,其后进行的步骤与上面基本一致
*/
@Override
public int del(T obj) {
//判断查询结果
if (query(obj) == null) {
return 0;
}
PreparedStatement ps = null;
Connection conn = null;
int result = 0;
try {
Class<? extends Object> clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
//设置权限
for (int i = 0; i< fields.length; i++) {
fields[i].setAccessible(true);
}
String sql = "delete from "+clazz.getSimpleName()+" where id = "+fields[0].get(obj)+"";
System.out.println(sql);
//获取连接
conn = JDBCUtil.instance.getConnection();
//预编译SQL
ps = conn.prepareStatement(sql);
result = ps.executeUpdate();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtil.instance.closeConnection(conn, null, ps);
}
return result;
}
/**
* 更新数据
* 思路:
* 模拟前端返回的数据是 一整条数据:即无论用户修改的是一行数据中的哪个字段,
* 我们都将那一整行数据返回,封装成一个对象传入update方法
* 根据传入对象,直接修改数据库中对应对象的数据
*/
@Override
public void update(T obj) {
PreparedStatement ps = null;
Connection conn = null;
try {
Class<? extends Object> clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
//字段
StringBuffer field = new StringBuffer();
//获取字段名拼接sql
fields[0].setAccessible(true);
//拼接SQL语句,因为直接略过了id字段,所以单独设置了id的权限
for (int i = 1; i< fields.length; i++) {
//设置权限,不包含id
fields[i].setAccessible(true);
if(i != fields.length-1) {
field = field.append(fields[i].getName()+" = ?,");
}else {
field = field.append(fields[i].getName()+" = ?");
}
}
String sql = "UPDATE "+clazz.getSimpleName()+" SET "+field+" where id = "+fields[0].get(obj)+"";
System.out.println(sql);
//获取连接
conn = JDBCUtil.instance.getConnection();
//预编译SQL
ps = conn.prepareStatement(sql);
for (int i = 1; i < fields.length; i++) {
ps.setObject(i, fields[i].get(obj));
}
ps.executeUpdate();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtil.instance.closeConnection(conn, null, ps);
}
}
/**
* 查询
* 根据传入对象的字节码对象获取到id字段
* 根据id字段获取数据(此处默认id字段在表中第一列,也实体类中第一个)
*/
@Override
public T query(T obj) {
PreparedStatement ps = null;
Connection conn = null;
ResultSet rs = null;
try {
Class<? extends Object> clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
fields[i].setAccessible(true);
}
String sql = "SELECT * from "+clazz.getSimpleName()+" where id = "+fields[0].get(obj)+"";
System.out.println(sql);
//获取连接
conn = JDBCUtil.instance.getConnection();
//预编译SQL
ps = conn.prepareStatement(sql);
rs = ps.executeQuery(sql);
while (rs.next()) {
for (int i = 0; i < fields.length; i++) {
fields[i].set(obj, rs.getObject(i+1));
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtil.instance.closeConnection(conn, rs, ps);
}
return obj;
}
@Override
/**
* 查询全部
*
*/
public List<T> queryAll(T obj) {
PreparedStatement ps = null;
Connection conn = null;
ResultSet rs = null;
List<T> list = new ArrayList<>();
T o = null;
try {
Class<? extends Object> clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
fields[i].setAccessible(true);
}
String sql = "SELECT * from "+clazz.getSimpleName()+"";
System.out.println(sql);
//获取连接
conn = JDBCUtil.instance.getConnection();
//预编译SQL
ps = conn.prepareStatement(sql);
rs = ps.executeQuery(sql);
while (rs.next()) {
//创建用于封装接收每一组数据的对象
o = (T) clazz.getConstructor().newInstance();
for (int i = 0; i < fields.length; i++) {
fields[i].set(o, rs.getObject(i+1));
}
//将对象封装到list中
list.add((T) o);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}finally {
JDBCUtil.instance.closeConnection(conn, rs, ps);
}
return list;
}
}
测试类
import java.util.List;
import org.junit.Test;
import cn.itsource.dao.impl.DaoImpl;
import cn.itsource.domain.Student;
import cn.itsource.domain.Teacher;
public class DaoTest {
DaoImpl<Object> dao = new DaoImpl<Object>();
@Test
public void testAdd() {
// Student s = new Student("瘦狗","123456",20,(byte)1,"世界和平");
Teacher t = new Teacher("骚猪",22,"男");
// dao.add(s);
dao.add(t);
}
@Test
public void testDel() {
Student s = new Student();
s.setId(5);
int del = dao.del(s);
System.out.println("删除"+del+"行");
}
@Test
public void testUpdate() {
Student s = new Student(6,"fdg","12456",21,true,"fds");
dao.update(s);
}
@Test
public void testQueryOne() {
Student s = new Student();
s.setId(5);
System.out.println(dao.query(s));
}
@Test
public void testQueryAll() {
/*Student s = new Student();
List<Object> queryAll = dao.queryAll(s);
for (Object object : queryAll) {
System.out.println(object);
}*/
Teacher teacher = new Teacher();
List<Object> queryAll2 = dao.queryAll(teacher);
for (Object object : queryAll2) {
System.out.println(object);
}
}
}
PreparedStatement
Statement: 表示静态SQL语句对象.
PreparedStatement:Statement的子接口,表示预编译SQL语句对象.
什么是预编译SQL语句
预编译语句PreparedStatement 是java.sql中的一个接口,它是Statement的子接口。通过Statement对象执行SQL语句时,需要将SQL语句发送给DBMS,由 DBMS首先进行编译后再执行。预编译语句和Statement不同,在创建PreparedStatement 对象时就指定了SQL语句,该语句立即发送给DBMS进行编译。当该编译语句被执行时,DBMS直接运行编译后的SQL语句,而不需要像其他SQL语句那样首先将其编译,再执行。
API示例:
上面语句中的问号便是占位符,即使用其把位置占住,之后我们在为其设置值,这样很好的避免了SQL注入问题