ORM简介与实战:Java+MySQL ORM框架实现

ORM简介与实战:Java+MySQL ORM框架实现

1.什么是ORM

​ ORM(Object-Relational Mapping)框架是一种在编程中广泛使用的技术,它实现了面向对象编程语言中的对象与关系数据库之间的映射。ORM框架的目的是为了简化数据库操作,让开发者可以用更加直观和面向对象的方式来处理数据库,而不是直接编写SQL语句。这样不仅可以提高开发效率,还能减少因为直接操作数据库而产生的错误。

1.1ORM的特点
  1. 对象与数据库表的映射:ORM框架允许开发者定义类(对象),这些类与数据库中的表相对应。类的属性对应于表的列,对象实例对应于表中的行。
  2. 数据访问抽象:通过ORM框架,开发者无需直接编写SQL语句来访问数据库。相反,他们可以使用框架提供的方法或API来执行查询、更新、删除等操作。

2.ORM实战

​ 技术栈

  • Java:作为编程语言。

  • MySQL:作为关系数据库。

  • 反射:用于动态创建对象和处理对象属性。

  • 自定义注解:用于标记类和属性,以便ORM框架识别。

  • 泛型:增强代码的复用性和类型安全。

  • JDBC:Java数据库连接技术,用于连接和操作数据库。

    步骤

2.1引入依赖jar包

在Maven项目的pom.xml文件中添加MySQL JDBC驱动和Lombok依赖:

<dependencies>
        <!--mysql-->
    <dependency>
         <groupId>mysql</groupId>
         <artifactId>mysql-connector-java</artifactId>
         <version>8.0.33</version>
    </dependency>
         <!--lombok用于自动生成get和set方法与构造器等-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.28</version>
    </dependency>
</dependencies>
2.2创建db.properties属性文件

在资源目录下创建db.properties文件,配置数据库连接信息:

jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/qy174?serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=123456
2.3创建DButil工具类

创建一个工具类DButil,用于加载数据库连接信息并获取数据库连接:

public class DButil  {
    private static String driverClassName="";
    private static String url="";
    private static String username="";
    private static String password="";
    static {
        try {
            Properties properties=new Properties();
            InputStream inputStream = ClassLoader.getSystemResourceAsStream("db.properties");
            properties.load(inputStream);
            driverClassName=properties.getProperty("jdbc.driverClassName");
            url=properties.getProperty("jdbc.url");
            username=properties.getProperty("jdbc.username");
            password=properties.getProperty("jdbc.password");
        } catch (IOException e) {
            throw new RuntimeException("无法读取db.properties");
        }
    }
    public static Connection getConnection() throws Exception{
        Class<?> aClass = Class.forName(driverClassName);
        Connection connection= DriverManager.getConnection(url,username,password);
        return connection;
    }
    public static void closeAll(Connection connection, PreparedStatement ps, ResultSet rs){
        try {
            if (rs!=null){
                rs.close();
            }
            if (ps!=null){
                ps.close();
            }
            if(connection!=null){
                connection.close();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}
2.4使用自定义注解

在类上使用,目的获得表名

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableName {
    String value();
}

在成员属性上使用,目的获取列名

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableField {
    String value();
}

在成员属性上使用,目的获取主键

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableId {
    String value() default "id";
}
2.5定义一个管理员实体类和Dao类,用于测试

Admin实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "Admin")
public class Admin {
    @TableId
    private Integer id;
    @TableField(value = "username")
    private String userName;
    private Integer password;
    private String name;
    private Integer gender;
    @TableField(value = "createtime")
    private Date createTime;
    private String head;
}

AdminDao类,继承BaseDao

public class AdminDao extends BaseDao<Admin>{ 
}
2.6实现BaseDao类

这个类将包含执行CRUD操作的方法。

public class BaseDao<T>  {
    private ResultSet rs=null;
    private Connection connection=null;
    private PreparedStatement ps=null;
    private int row= 0;
    private Class<T> clazz;
    /**
     * 通用添加方法 insert into 表名(column...) values(值...)
     * @param  t 需要插入的数据对象,泛型T
     * @return row 返回影响的行数
     * @throws Exception 如果操作过程中出现异常,会抛出异常
     */
    public int insert(T t)throws Exception{
        // 初始化SQL语句,准备插入数据
        StringBuffer sql=new StringBuffer("insert into ");
        // 获取数据对象的类信息
        Class<?> aClass = t.getClass();
        // 尝试从类上获取表名注解,用于确定插入的表名
        TableName tableName = aClass.getAnnotation(TableName.class);
        // 默认使用类名作为表名
        String name= aClass.getSimpleName();
        // 如果存在表名注解,则使用注解中的表名
        if (tableName!=null){
            name=tableName.value();
        }
        // 将表名追加到SQL语句中
        sql.append(name);
        // 获取类的所有字段信息
        Field[] declaredFields = aClass.getDeclaredFields();
        // 用于存储字段名
        List arrayList=new ArrayList<>();
        // 用于存储字段的值
        List arrayList1=new ArrayList<>();
        // 遍历所有字段,处理字段名和字段值
        for (Field declaredField : declaredFields) {
            // 检查字段是否为ID字段,如果是则跳过
            TableId tableId = declaredField.getAnnotation(TableId.class);
            if (tableId!=null){
                continue;
            }
            // 获取字段名,如果存在字段注解,则使用注解中的字段名
            String field=declaredField.getName();
            TableField tableField = declaredField.getAnnotation(TableField.class);
            if (tableField!=null){
                field=tableField.value();
            }
            // 将字段名追加到字段名列表中
            arrayList.add(field);
            // 设置字段可访问,以便获取字段值
            declaredField.setAccessible(true);
            // 获取字段值,并将其包装在单引号中,追加到字段值列表中
            Object o = declaredField.get(t);
            arrayList1.add("'"+o+"'");
        }
        // 将字段名列表转换为SQL语句中的格式
        String replace = arrayList.toString().replace("[", "(").replace("]", ")");
        // 将字段值列表转换为SQL语句中的格式
        String replace1 = arrayList1.toString().replace("[", "(").replace("]", ")");
        // 将字段名和字段值追加到SQL语句中
        sql.append(replace);
        sql.append(" values ");
        sql.append(replace1);
        // 尝试执行插入操作
        try {
            // 获取数据库连接
            connection= DButil.getConnection();
            // 准备SQL语句
            ps=connection.prepareStatement(sql.toString());
            // 执行插入操作,返回影响的行数
            row = ps.executeUpdate();
        } catch (Exception e) {
            // 如果出现异常,抛出运行时异常
            throw new RuntimeException(e);
        } finally {
            // 关闭数据库连接及相关资源
            DButil.closeAll(connection,ps,rs);
        }
        // 返回影响的行数
        return row;
    }

    /**
     * 通过反射机制获取类的字段信息,构造更新语句,然后执行更新操作。
     * 通用修改方法 update 表名 set column=value,...,column=value where 主键=value
     * @param t 要更新的对象,必须是注解了@TableName和@TableField的类的实例。
     * @return row 返回影响的行数。
     * @throws Exception 如果操作过程中出现异常,则抛出。
     */
    public int update(T t) throws Exception{
        // 初始化SQL语句,开始构建更新语句。
        StringBuffer sql=new StringBuffer("update ");
        // 获取对象的类,用于后续获取类的注解和字段信息。
        Class<?> aClass = t.getClass();
        // 尝试从类上获取@TableName注解,用于获取表名。
        TableName tableName = aClass.getAnnotation(TableName.class);
        // 获取类的简单名称,作为默认表名。
        String name= aClass.getSimpleName();
        // 如果类上有@TableName注解,则使用注解的值作为表名。
        if (tableName!=null){
            name=tableName.value();
        }
        // 构建SQL语句的更新部分。
        sql.append(name+ "  set ");
        // 获取类的所有声明字段,用于后续遍历构建更新语句的字段部分。
        Field[] declaredFields = aClass.getDeclaredFields();
        // 初始化WHERE子句的起始部分。
        String where=" where ";
        // 遍历所有字段,构建更新语句的字段部分和WHERE子句。
        for (Field declaredField : declaredFields) {
            // 获取字段名。
            // 属性名
            String field = declaredField.getName();
            // 尝试从字段上获取@TableField注解,用于获取字段在数据库中的名称。
            TableField tableField = declaredField.getAnnotation(TableField.class);
            // 尝试从字段上获取@TableId注解,用于识别主键字段。
            TableId tableId = declaredField.getAnnotation(TableId.class);
            // 设置字段可访问,以读取字段值。
            declaredField.setAccessible(true);
            // 获取字段的值。
            Object value = declaredField.get(t);
            // 如果字段是主键字段,则构建WHERE子句的一部分。
            // 如果为主键注解
            if(tableId!=null){
                String tableIdName=tableId.value();
                where+=tableIdName+"='"+value+"'";
                continue;
            }
            // 如果字段上有@TableField注解,则使用注解的值作为数据库中的字段名。
            if(tableField!=null){
                field = tableField.value();
            }
            // 构建更新语句的字段部分。
            sql.append(field+"='"+value+"',");
        }
        // 删除最后一个逗号,完成更新语句的构建。
        sql.deleteCharAt(sql.length()-1).append(where);
        // 从DButil中获取数据库连接。
        // 执行sql语句
        Connection conn = DButil.getConnection();
        // 准备执行更新语句。
        PreparedStatement ps = conn.prepareStatement(sql.toString());
        // 执行更新操作,返回影响的行数。
        int row = ps.executeUpdate();
        // 返回影响的行数。
        return row;
    }

    /**
     * 构造方法
     */
    public BaseDao(){
        //this表示子类Dao对象
        Class<? extends BaseDao> aClass = this.getClass();
        //获取当前子类的父类的反射类
        ParameterizedType genericSuperclass = (ParameterizedType) aClass.getGenericSuperclass();
        //获取该反射类中的泛型类型
        Type actualTypeArgument = genericSuperclass.getActualTypeArguments()[0];
        clazz = (Class) actualTypeArgument;
    }

    /**
     * 根据ID删除数据库中的记录。
     * 删除通用方法 delete from 表名 where 主键=value
     * @param id 主键值,用于确定要删除的记录。
     * @return 返回受影响的行数,即被删除的行数。
     * @throws Exception 如果数据库操作过程中发生错误,则抛出异常。
     */
    public int delete(Object id) throws Exception{
        // 初始化SQL语句,开始构建删除语句
        StringBuffer sql=new StringBuffer("delete from ");
        // 获取实体类的简单类名,用于确定表名
        String tableName=clazz.getSimpleName();//实体类的名称
        // 查找类上的TableName注解,以获取可能指定的表名
        TableName annotation = clazz.getAnnotation(TableName.class);
        // 如果找到了TableName注解,则使用注解中指定的表名
        if(annotation!=null){
            tableName = annotation.value();
        }
        // 在SQL语句中添加表名
        sql.append(tableName+" where ");

        // 遍历类中所有声明的字段,寻找带有TableId注解的字段,以确定主键字段
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field field:declaredFields){
            TableId tableId = field.getAnnotation(TableId.class);
            // 如果找到了TableId注解,则确定了主键字段,构建完整的SQL语句
            if(tableId!=null){
                sql.append(tableId.value()+"="+id);
                break;
            }
        }
        // 从DButil中获取数据库连接
        //执行sql语句
        Connection conn = DButil.getConnection();
        // 根据构建的SQL语句创建PreparedStatement对象
        PreparedStatement ps = conn.prepareStatement(sql.toString());
        // 执行更新操作,删除记录
        int row = ps.executeUpdate();
        // 返回受影响的行数
        return row;
    }
}
2.7简单测试

使用juit测试

public class Test01 {

    AdminDao adminDao=new AdminDao();
    @Test
    public void testInsert() throws Exception{
        SimpleDateFormat time = new SimpleDateFormat("yyyy-MM-dd");
        Date now = new Date();
        String formattedDate = time.format(now);
        LocalDate ld=LocalDate.parse(formattedDate);
        Admin admin=new Admin(23,"f",123,"范",1,java.sql.Date.valueOf(ld),"hhh");
        adminDao.update(admin);

    }
    @Test
    public void testDelete() throws Exception{
        adminDao.delete(23);
    }
}

以上步骤为构建ORM框架的基础,接下来可以进一步实现ORM的核心功能,如对象与表的映射、SQL语句的自动生成与执行等。由于篇幅限制,这里仅展示了数据库连接部分的实现。在实际项目中,还需要结合反射、自定义注解等技术来完成完整的ORM框架开发。

  • 14
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值