手写一个简单的持久层框架

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());
    }

测试结果:

在这里插入图片描述

  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
很抱歉,作为AI语言模型,我无法代码,但我可以简单地介绍一下MyBatis框架的基本原理和核心组件。 MyBatis是一款Java持久框架,它的主要作用是将Java对象和关系型数据库之间的操作映射起来,从而实现数据的持久化。MyBatis的核心组件包括: 1. SqlSessionFactoryBuilder:用于创建SqlSessionFactory。通过读取MyBatis的配置文件和Mapper文件,生成SqlSessionFactory,从而获取SqlSession。 2. SqlSessionFactory:是MyBatis框架的核心组件,用于创建SqlSession。通过SqlSessionFactory,我们可以获取SqlSession对象,从而进行数据库的操作。 3. SqlSession:是MyBatis框架中的一个关键组件,它表示和数据库之间的会话。SqlSession提供了一系列方法,用于执行SQL语句、提交事务、关闭连接等操作。 4. Mapper:是MyBatis框架中的另一个重要组件,它用于定义数据库操作的接口。Mapper接口中定义了一系列方法,每个方法对应一个SQL语句。在Mapper接口中,我们可以使用注解或XML文件来配置SQL语句。 基于以上核心组件,我们可以通过编Mapper接口和对应的SQL语句,使用MyBatis框架来进行数据库的操作。下面是一个简单的Mapper接口示例: ``` public interface UserMapper { @Select("SELECT * FROM user WHERE id = #{id}") User getUserById(int id); @Insert("INSERT INTO user(name, age) VALUES (#{name}, #{age})") void addUser(User user); @Update("UPDATE user SET name = #{name}, age = #{age} WHERE id = #{id}") void updateUser(User user); @Delete("DELETE FROM user WHERE id = #{id}") void deleteUser(int id); } ``` 在这个示例中,我们定义了一个UserMapper接口,其中包含了4个方法,分别对应了查询、插入、更新和删除操作。每个方法都使用@Select、@Insert、@Update、@Delete注解来配置对应的SQL语句。使用MyBatis框架时,我们只需要通过SqlSession.getMapper(UserMapper.class)方法获取UserMapper接口的实例,然后调用其中的方法即可完成对数据库的操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值