1.前言
1.1 什么是持久层
与数据库交互的一层称为持久层(dao)。用于完成orm操作。
1.2 什么是orm
o:(Object对象) r:(relative关系) m:(mapping映射)。
对应关系:实体类:数据库表 属性:表的字段 实体类对象:一条记录 集合:表中多条记录。
1.3 框架的内容
自己编写持久层框架,可以完成无需写sql语句即可完成对单表的CRUD操作。
1.4 需要用到的技术栈
java+mysql+反射+自定义注解+泛型+jdbc
2. 正文
2.1 创建一个maven的java工程
2.2 引入依赖jar包
主要为MySQL与德鲁伊jar包
<dependencies>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!--阿里巴巴的德鲁伊-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.16</version>
</dependency>
<!--lombok用于自动生成get和set方法与构造器等-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>
<!--junit单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
2.3 创建数据源的属性文件
driverClassName = com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai
username = root
password = 123456
2.4 创建DbUtil工具类
package com.fwx.util;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
public class DbUtil {
private static DataSource dataSource;
// 静态块资源,只执行一次
static {
try {
// 创建文件对象
Properties properties = new Properties();
// 加载数据文件
InputStream inputStream = DbUtil.class.getClassLoader().getResourceAsStream("db.properties");
properties.load(inputStream);
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
// 获取连接对象
public static Connection getConn(){
Connection conn = null;
try {
conn = dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
// 关闭资源
public static void closeAll(Connection conn, PreparedStatement ps, ResultSet rs){
try {
if (rs != null){
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (ps != null){
ps.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (conn != null){
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
2.5 注解文件
2.5.1 表示表名的注解
package com.fwx.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 当数据库的表名于实体类的类名不相同时,用于类上,value值为表名
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableName {
public String value();
}
2.5.2 表示列名的注解
package com.fwx.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 当数据库表的列名和实体类的属性名不相同时使用,value值表示列名
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableField {
String value();
}
2.5.3 表示主键的注解
package com.fwx.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 用于数据库表中主键对应的实体类属性上,value值表示数据表的主键名
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableFieldId {
String value() default "id";
}
2.6 测试用实体类以及dao类
测试实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName(value = "t_role")
public class Role {
@TableFieldId
private Integer id;
@TableField("role_name")
private String roleName;
@TableField("role_code")
private String roleCode;
private String description;
}
测试dao类
public class RoleDao extends BaseDao<Role> {
}
2.7 增加功能
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
// 向数据库中添加一个数据
public int insert(T t){
try {
// 创建sql字符串
StringBuffer sql = new StringBuffer("insert into ");
// 获取表名
// 通过对象得到该对象的反射类
Class<?> aClass = t.getClass();
// 获取实体类的名称
String tableName = aClass.getSimpleName();
// 通过反射类获取该类上的TableName注解,用于表名和实体类名不一致的情况
TableName annotation = aClass.getAnnotation(TableName.class);
if (annotation != null){
// 通过注解获取注解中的value值(value值为表名)
tableName = annotation.value();
}
// 拼接sql语句
sql.append(tableName);
// 获取所有列名和属性值
// 创建列名集合
List<String> columnNames = new ArrayList<>();
// 创建属性值集合
List<Object> values = new ArrayList<>();
// 获取所有的属性
Field[] declaredFields = aClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
// 判断是否为主键,这里是主键则不放入集合中,因为表的主键设置的是自动递增,不需要赋值
TableFieldId fieldId = declaredField.getAnnotation(TableFieldId.class);
if (fieldId != null){
continue;
}
// 获取属性名
String name = declaredField.getName();
// 判断是否有属性名注解,加入这个手写的注解代表属性名和列名不一致,需要替换成列名
TableField field = declaredField.getAnnotation(TableField.class);
if (field != null){
name = field.value();
}
// 把列名放到集合中
columnNames.add(name);
// 设置允许访问私有属性,由于实体类中的属性是用private修饰的,不设置则无法获取到属性的值
declaredField.setAccessible(true);
// 获取每一项的属性值
Object value = declaredField.get(t);
// 把属性值放到属性值集合中,由于字符串需要用''引着,而数字用''引着MySQL数据库也能识别,所以都用''引着
values.add("'"+value+"'");
}
// 把列名集合和属性值结合的[]修改为()
String replaceName = columnNames.toString().replace("[", "(").replace("]", ")");
String replaceValues = values.toString().replace("[", "(").replace("]", ")");
// 拼接到sql中
sql.append(replaceName+" values "+replaceValues);
System.out.println(sql);
// 执行sql语句
conn = DbUtil.getConn();
ps = conn.prepareStatement(sql.toString());
// 返回受影响的行数
return ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}finally {
// 关闭资源
DbUtil.closeAll(conn,ps,rs);
}
return 0;
}
测试代码:
RoleDao roleDao = new RoleDao();
@Test
public void insertTest(){
Role role = new Role(null,"学生1","student1","学生1");
int insert = roleDao.insert(role);
System.out.println(insert);
}
测试结果:
2.8 修改功能
// 修改数据库中的数据
public int update(T t){
try {
// 创建sql字符串
StringBuffer sql = new StringBuffer("update ");
// 创建where语句的字符串
String where = " where ";
// 获取表名
// 通过对象得到该对象的反射类
Class<?> aClass = t.getClass();
// 获取实体类的名称
String tableName = aClass.getSimpleName();
// 通过反射类获取该类上的TableName注解,用于表名和实体类名不一致的情况
TableName annotation = aClass.getAnnotation(TableName.class);
if (annotation != null){
// 通过注解获取注解中的value值(value值为表名)
tableName = annotation.value();
}
// 拼接sql语句
sql.append(tableName + " set ");
// 获取所有列名和属性值
Field[] declaredFields = aClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
// 设置允许访问私有属性,由于实体类中的属性是用private修饰的,不设置则无法获取到属性的值
declaredField.setAccessible(true);
// 获取对应的属性值
Object value = declaredField.get(t);
// 判断是否为主键,是主键则执行之后跳出本次循环
TableFieldId fieldId = declaredField.getAnnotation(TableFieldId.class);
if (fieldId != null){
String id = fieldId.value();
// 拼接where语句
where += id + " = '" + value +"'";
continue;
}
// 获取属性名
String name = declaredField.getName();
// 判断是否有属性名注解,加入这个手写的注解代表属性名和列名不一致,需要替换成列名
TableField field = declaredField.getAnnotation(TableField.class);
if (field != null){
name = field.value();
}
// 拼接sql语句
sql.append(name + " = '" + value +"' ,");
}
// 把拼接好的sql语句去掉最后一个值后面的",",因为此时还没有拼接where语句,所以直接删除字符串最后一位即可
// 删除之后拼接where语句
sql.deleteCharAt(sql.length()-1).append(where);
// 执行sql语句
conn = DbUtil.getConn();
ps = conn.prepareStatement(sql.toString());
// 返回影响行数
return ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭资源
DbUtil.closeAll(conn,ps,rs);
}
return 0;
}
测试代码:
@Test
public void updateTest(){
Role role = new Role(18,"学生2","student2","学生2");
int update = roleDao.update(role);
System.out.println(update);
}
测试结果:
2.9 删除功能
// 删除数据库中的数据
public int delete(int id){
try {
StringBuffer sql = new StringBuffer("delete from ");
// this表示子类Dao对象
Class<? extends BaseDao> aClass = this.getClass();
// 获取当前子类的父类的反射类,这里要强转成Type的子类ParameterizedType类,因为子类拥有更多的功能
ParameterizedType genericSuperclass = (ParameterizedType) aClass.getGenericSuperclass();
// 获取该反射类中的泛型类型
Type actualTypeArgument = genericSuperclass.getActualTypeArguments()[0];
Class<?> clazz = (Class<?>) actualTypeArgument;
// 获取实体类的名称
String tableName = clazz.getSimpleName();
// 通过反射类获取该类上的TableName注解,用于表名和实体类名不一致的情况
TableName annotation = clazz.getAnnotation(TableName.class);
if (annotation != null){
// 通过注解获取注解中的value值(value值为表名)
tableName = annotation.value();
}
// 拼接sql语句
sql.append(tableName + " where ");
// 获取所有列名和属性值
Field[] declaredFields = clazz.getDeclaredFields();
// 遍历找到主键的属性名
for (Field declaredField : declaredFields) {
TableFieldId fieldId = declaredField.getAnnotation(TableFieldId.class);
if (fieldId != null){
String name = fieldId.value();
// 拼接sql语句
sql.append(name + " = " +id);
break;
}
}
// 执行sql语句
conn = DbUtil.getConn();
ps = conn.prepareStatement(sql.toString());
return ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
DbUtil.closeAll(conn,ps,rs);
}
return 0;
}
测试代码:
@Test
public void delete(){
System.out.println(roleDao.delete(18));
}
测试结果:
2.10 根据主键查询
// 通过id查询表中的内容
public T selectById(Object id){
try {
StringBuffer sql = new StringBuffer("select * from ");
Class<? extends BaseDao> aClass = this.getClass();
ParameterizedType genericSuperclass = (ParameterizedType) aClass.getGenericSuperclass();
Type actualTypeArgument = genericSuperclass.getActualTypeArguments()[0];
Class<T> clazz = (Class<T>) actualTypeArgument;
// 获取表名
String tableName = clazz.getSimpleName();
TableName annotation = clazz.getAnnotation(TableName.class);
if (annotation != null){
tableName = annotation.value();
}
sql.append(tableName + " where ");
// 获取主键名
Field[] declaredFields = clazz.getDeclaredFields();
for (Field declaredField : declaredFields) {
TableFieldId fieldId = declaredField.getAnnotation(TableFieldId.class);
if (fieldId != null){
sql.append(fieldId.value() + " = " + id);
break;
}
}
// 执行sql
conn = DbUtil.getConn();
ps = conn.prepareStatement(sql.toString());
rs = ps.executeQuery();
// 封装数据到实体类
while (rs.next()){
// 创建实体类对象
T t = clazz.newInstance();
// 遍历属性
for (Field declaredField : declaredFields) {
// 获取属性名
String name = declaredField.getName();
TableFieldId fieldId = declaredField.getAnnotation(TableFieldId.class);
if (fieldId != null){
name = fieldId.value();
}
TableField tableField = declaredField.getAnnotation(TableField.class);
if (tableField != null){
name = tableField.value();
}
// 设置允许访问私有属性
declaredField.setAccessible(true);
// 获取数据库中指定列的值
Object value = rs.getObject(name);
// 为指定对象的属性赋值
declaredField.set(t,value);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭资源
DbUtil.closeAll(conn,ps,rs);
}
return null;
}
测试代码:
@Test
public void selectById(){
System.out.println(roleDao.selectById(5));
}
测试结果:
2.11 查询所有信息
// 查询所有信息
public List<T> selectAll(){
try {
StringBuffer sql = new StringBuffer("select * from ");
Class<? extends BaseDao> aClass = this.getClass();
ParameterizedType genericSuperclass = (ParameterizedType) aClass.getGenericSuperclass();
Type actualTypeArgument = genericSuperclass.getActualTypeArguments()[0];
Class<T> clazz = (Class<T>) actualTypeArgument;
// 获取表名
String tableName = clazz.getSimpleName();
TableName annotation = clazz.getAnnotation(TableName.class);
if (annotation != null){
tableName = annotation.value();
}
sql.append(tableName);
// 执行sql
conn = DbUtil.getConn();
ps = conn.prepareStatement(sql.toString());
rs = ps.executeQuery();
// 创建对象集合
List<T> list = new ArrayList<>();
// 封装数据到实体类
while (rs.next()){
// 创建实体类对象
T t = clazz.newInstance();
// 遍历属性集合
Field[] declaredFields = clazz.getDeclaredFields();
for (Field declaredField : declaredFields) {
// 获取属性名
String name = declaredField.getName();
TableFieldId fieldId = declaredField.getAnnotation(TableFieldId.class);
if (fieldId != null){
name = fieldId.value();
}
TableField tableField = declaredField.getAnnotation(TableField.class);
if (tableField != null){
name = tableField.value();
}
// 设置允许访问私有属性
declaredField.setAccessible(true);
// 获取数据库中指定列的值
Object value = rs.getObject(name);
// 为指定对象的属性赋值
declaredField.set(t,value);
}
list.add(t);
}
return list;
} catch (Exception e) {
e.printStackTrace();
} finally {
DbUtil.closeAll(conn,ps,rs);
}
return null;
}
测试代码:
@Test
public void selectAll(){
System.out.println(roleDao.selectAll().toString());
}
测试结果: