从零开始学Java之反射的高级玩法,手写一个ORM框架

作者 :孙玉昌,昵称【一一哥 】,另外【壹壹哥】也是我哦

千锋教育高级教研员、CSDN博客专家、万粉博主、阿里云专家博主、掘金优质作者

前言

在上一篇文章中,壹哥 给大家讲解了反射的基本使用,但受限于篇幅,我们并没有把反射的知识全都学完。在今天的这篇文章中,壹哥会继续往下讲解反射的高级玩法,希望大家集中精力,继续认真学习哦。

------------------------------前戏已做完,精彩即开始----------------------------

全文大约【4200】 字,不说废话,只讲可以让你学到技术、明白原理的纯干货!本文带有丰富的案例及配图视频,让你更好地理解和运用文中的技术概念,并可以给你带来具有足够启迪的思考......

配套开源项目资料

Github: github.com/SunLtd/Lear...

Gitee: gitee.com/sunyiyi/Lea...

一. 操作注解

我们知道,注解是一种特殊的代码标记,它们可以被附加到类、方法、变量等代码元素上,用于提供额外的信息和描述。实际上,注解也可以通过反射来获取到,并根据注解的信息来实现相应的功能。接下来壹哥就给大家设计一个简单的案例,看看反射是如何操作注解的。

1. 定义注解

首先,我们来定义一个注解,这里我们定义下面这样一个@MyAnnotation注解:

java 复制代码
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author 一一哥Sun
 * @company 千锋教育
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
    //注解的属性值
    String value() default "default value";
}

在上述代码中,@MyAnnotation注解的生命周期使用@Retention来指定,作用目标使用@Target来指定。我们把@MyAnnotation注解的生命周期限定为RUNTIME,表示该注解可以在运行时被反射技术获取。大家注意,要想在反射中使用该注解,必须把生命周期限定为RUNTIME。

另外我们还使用@Target(ElementType.METHOD)来限定@MyAnnotation的作用目标是在方法上。并且@MyAnnotation注解还有一个value属性,类型是String,默认值为"default value"。

2. 使用注解

定义了注解之后,我们就可以在代码中使用它了。例如,我们可以给一个方法添加@MyAnnotation注解:

java 复制代码
import java.lang.reflect.Method;

/**
 * @author 一一哥Sun
 * @company 千锋教育
 */
public class Demo04 {
    // 给该方法添加一个自定义的注解
    @MyAnnotation("hello,一一哥")
    public void myMethod() {
        System.out.println("反射加注解,法力大无边");
    }
}

上述代码中,我们在myMethod()方法上添加了@MyAnnotation("hello,一一哥")注解,表示该方法可以使用@MyAnnotation注解提供的功能。

3. 反射获取注解

使用注解之后,我们就可以通过反射技术来获取注解里的信息,并根据注解的信息来实现相应的功能了。例如,我们可以使用反射类java.lang.reflect.Method的getAnnotation()方法,来获取该方法上的@MyAnnotation注解:

java 复制代码
import java.lang.reflect.Method;

/**
 * @author 一一哥Sun
 * @company 千锋教育
 */
public class Demo04 {
    // 给该方法添加一个自定义的注解
    @MyAnnotation("hello,一一哥")
    public void myMethod() {
        System.out.println("反射加注解,法力大无边");
    }

    public static void main(String[] args) {
        try {
            Demo04 obj = new Demo04();
            // 获取到obj对象里的myMethod方法
            Method method = obj.getClass().getMethod("myMethod");
            // 获取该方法上的MyAnnotation注解
            MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);

            if (annotation != null) {// 判断注解是否为空
                // 获取注解的value属性值
                String value = annotation.value();
                //value= hello,一一哥
                System.out.println("value= " + value);
            }
        } catch (SecurityException | IllegalArgumentException | NoSuchMethodException e) {
             e.printStackTrace();
        }
    }
}

上述代码,我们首先创建了一个Demo04对象,并通过反射获取到了myMethod()方法的Method对象。然后,我们使用getAnnotation(MyAnnotation.class)方法,获取到了该方法上的@MyAnnotation注解。如果该方法上没有@MyAnnotation注解,则返回null。如果存在该注解,就可以通过注解对象中的方法来获取注解的信息了。

4. 反射解析注解

我们除了可以获取注解信息之外,还可以使用反射技术来解析注解,并根据注解的信息来实现相应的功能。例如,我们可以使用反射类java.lang.reflect.Method的isAnnotationPresent()方法,来判断某个方法上是否包含@MyAnnotation注解:

java 复制代码
import java.lang.reflect.Method;

/**
 * @author 一一哥Sun
 * @company 千锋教育
 */
public class Demo04 {
    // 给该方法添加一个自定义的注解
    @MyAnnotation("hello,一一哥")
    public void myMethod() {
        System.out.println("反射加注解,法力大无边");
    }

    public static void main(String[] args) {
        try {
            Demo04 obj = new Demo04();
            // 获取到obj对象里的myMethod方法
            Method method = obj.getClass().getMethod("myMethod");
            
            //反射解析注解
            if (method.isAnnotationPresent(MyAnnotation.class)) {
                MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
                // 获取注解的value属性值
                String value = annotation.value(); 
                System.out.println("value= " + value);
            }
        } catch (SecurityException | IllegalArgumentException | NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

在上述代码中,我们首先创建了一个Demo04对象,并获取到了myMethod()方法的Method对象。然后,我们又使用isAnnotationPresent(MyAnnotation.class)方法,来判断该方法上是否包含@MyAnnotation注解。如果存在该注解,则使用getAnnotation(MyAnnotation.class)方法来获取注解对象,并根据注解对象的方法来实现相应的功能。

这样,通过以上这个案例,壹哥 就带大家实现了反射对注解的操作,你学会了吗?接下来壹哥再给大家设计一个更高级的案例,咱们来定义一个自己的ORM框架,看看框架都是怎么开发的。

二. ORM框架简介

1. 概念

ORM(Object-Relational Mapping)是一种可以将对象模型(比如实体类)映射到关系模型(比如数据库表)的技术, 目前常见的Java ORM框架有Hibernate、MyBatis、Spring Data JPA、Mybatis-Plus等,这些框架在我们的线下课程中都有详细讲解。这种框架允许我们使用面向对象的方式来操作数据库,不需要编写任何的SQL语句,从而提高了开发效率和代码可维护性,大大简化了数据库编程的复杂性。

通常,ORM框架会给我们提供一个持久化层,负责将Java对象映射到数据库表,并提供一些常见的CRUD(Create, Read, Update, Delete)操作。从底层实现来看,ORM框架通常是使用反射和注解来实现的

所以现在我们其实已经具备了编写一个简单ORM框架的技术基础了,但在具体实现之前,壹哥还要给大家梳理一下ORM框架要具备的功能。

2. 设计ORM框架

其实做软件开发,首先得知道自己想要一个什么东西,如果你连自己想要什么都不知道,那又开发什么呢?所以在设计ORM框架之前,我们需要明确ORM框架应该具备的功能,一个基本的ORM框架通常需要实现以下功能:

  1. 连接和关闭数据库;
  2. 数据库的事务管理;
  3. 对象与数据库表的映射;
  4. 数据库的查询和更新;
  5. 数据库结果集与对象之间的映射

明确了ORM框架应该具备的基本功能之后,后面我们就可以进行开发了。

3. 分析ORM框架实现思路

接下来壹哥再给大家分析一下ORM框架的实现思路。

3.1 连接数据库和关闭

我们可以使用JDBC来连接MySQL数据库,并在框架初始化时完成数据库连接。在框架关闭时,需要关闭数据库连接。

关于JDBC,如果你是初学者,可能对此还不太清楚,后面壹哥 会给大家再开辟一个MySQL数据库专栏,到时候会专门讲解JDBC的操作,欢迎持续关注壹哥哦,本文请各位简单跟着学习即可。

3.2 事务管理

我们可以使用JDBC的事务管理来实现ORM框架的事务管理。在框架中,我们可以定义一个@Transactional注解,表示该方法需要在事务中执行。在执行该方法时,首先需要开启事务,然后执行方法体,如果方法执行成功,则提交事务,否则回滚事务。

因为大家现在还没有学习数据库的知识,所以今天的文章就不给大家实现事务功能了。

3.3 对象到数据库表的映射

我们可以自定义注解来实现对象与数据库表的映射。在框架中,我们可以定义一个@Table注解和一个@Column注解,分别表示数据库表和表字段。在实体类中,我们可以使用@Table注解来指定该类对应的数据库表,使用@Column注解来指定该属性对应的数据库字段。

3.4 数据库查询和更新

我们可以使用JDBC来实现ORM框架的数据库查询和更新。在框架中,我们可以定义一个QueryRunner类,来封装JDBC的查询和更新操作。在执行查询和更新时,我们可以使用反射来获取实体类对应的表名和字段名,然后生成相应的SQL语句。

3.5 数据库结果集与对象之间的映射

我们也可以使用反射来实现数据库结果集与对象之间的映射。在框架中,我们可以定义一个ResultSetHandler类,来封装JDBC的结果集处理操作。在处理结果集时,我们可以使用反射来获取实体类对应的属性名和类型,然后将结果集中的数据按照属性名和类型设置到实体类的属性中。

在本文中,壹哥 只给大家实现添加操作,其余的功能当做作业,留给大家自行实现。如果你在实现的过程中有什么问题,可以在评论区给壹哥留言或私信,我看到了就会给大家及时解答。

三. 实现ORM框架

接下来壹哥就给大家讲一下ORM框架的具体实现过程,核心代码如下。

1. 必要准备

为了方便我们后面的测试,大家可以提前自行准备好一个数据库及对应的表格,壹哥在MySQL中准备了一个db1数据库,并在其中创建了user表。

除此之外,大家还有先下载一个mysql的依赖包,并导入到自己的项目中,如下图所示:

2. 定义注解

接着,我们就要定义两个注解:@Table和@Column,用于指定实体类对应的数据库表和表字段。

2.1 @Table

java 复制代码
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author 一一哥Sun
 * @company 千锋教育
 * 自定义注解--获取表名
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Table {
    //关联表名
    String name();
}

@Table注解用于标记实体类对应的数据库表,其中name()方法用于指定数据库表名。

2.2 @Column

java 复制代码
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author 一一哥Sun
 * @company 千锋教育 自定义注解--获取列名
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
    // 关联列名
    String name();
}

@Column注解用于标记实体类属性对应的数据库字段,其中name()方法用于指定数据库字段名。

3. 定义实体类

然后,我们需要定义实体类,使用@Table注解指定实体类对应的数据库表,使用@Column注解指定实体类属性对应的数据库字段。

java 复制代码
import demo37.orm.annotation.Column;
import demo37.orm.annotation.Table;

/**
 * @author 一一哥Sun
 * @company 千锋教育
 */
@Table(name = "user")//关联自定义注解@Table,将类名与表名建立映射
public class User {
    //关联自定义注解@Column,将属性名与列名建立映射
    @Column(name = "id")
    private int id;

    @Column(name = "username")
    private String username;

    @Column(name = "age")
    private int age;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User [id=" + id + ", username=" + username + ", age=" + age + "]";
    }   
}

上述代码定义了一个User类,其中@Table(name = "user")注解指定了该类对应的数据库表为users,@Column(name = "id")、@Column(name = "username")、@Column(name = "age")注解指定了该类属性对应的数据库字段为id、username、age。

4. 定义ORM框架

我们可以定义一个ORM类,用于实现将对象映射到数据库表中的功能。

java 复制代码
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import demo37.orm.annotation.Column;
import demo37.orm.annotation.Table;

/**
 * @author 一一哥Sun
 * @company 千锋教育
 * ORM框架的核心类
 */
public class ORM {
    private Connection conn;

    //构造方法中设置数据库的url、用户名和密码
    public ORM(String url, String user, String password) {
        try {
        	//通过反射的第三种获取字节码方式来获取字节码对象
        	//加载Driver驱动
            Class.forName("com.mysql.jdbc.Driver");
            //获取数据库Connection连接对象
            conn = DriverManager.getConnection(url, user, password);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 定义一个数据库添加数据的方法
     * @param obj,实体类对象
     */
    public void save(Object obj) {
        Class clazz = obj.getClass();
        //得到某个实体对象上绑定的Table注解
        Table table = (Table) clazz.getAnnotation(Table.class);
        //得到实体类绑定的表名
        String tableName = table.name();

        //拼接insert into插值语句
        StringBuilder sb = new StringBuilder("INSERT INTO ");
        sb.append(tableName).append(" (");

        //获取实体类中每个属性上关联的列名
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            Column column = field.getAnnotation(Column.class);
            if (column != null) {
                //将列名拼接到insert语句中
                sb.append(column.name()).append(",");
            }
        }
        //去掉insert语句中多余的","
        sb.deleteCharAt(sb.length() - 1).append(") VALUES (");

        //在insert语句中,拼接values语句后面的值
        for (Field field : fields) {
            Column column = field.getAnnotation(Column.class);
            if (column != null) {
                //对私有属性设置可访问性
                field.setAccessible(true);
                try {
                    sb.append("'").append(field.get(obj)).append("',");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        //去除insert语句中最后多余的","
        sb.deleteCharAt(sb.length() - 1).append(")");

        try {
        	//执行SQL语句
            Statement stmt = conn.createStatement();
            stmt.executeUpdate(sb.toString());
            stmt.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 关闭数据库连接
     */
    public void close() {
        try {
            conn.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

上述代码定义了一个ORM类,其中save()方法用于将对象保存到数据库表中,close()方法用于关闭数据库连接。save()方法首先使用反射获取对象的类信息和注解信息,然后根据注解信息生成SQL语句并执行插入操作。

5. 使用ORM框架

现在,我们可以使用ORM框架将实体类对象映射到数据库表中。

java 复制代码
import demo37.orm.core.ORM;
import demo37.orm.entity.User;

/**
 * @author 一一哥Sun
 * @company 千锋教育
 */
public class Demo05 {
    public static void main(String[] args) {
        //传入自己的数据库表的地址与账户信息
        ORM orm = new ORM("jdbc:mysql://localhost:3306/db1?useSSL=false", "root", "syc");
        //创建一个用户对象
        User user = new User();
        user.setId(55);
        user.setUsername("一一哥666");
        user.setAge(18);
        //调用ORM框架的插入操作
        orm.save(user);
        //关闭数据库连接
        orm.close();
    }
}

上述代码首先创建了一个ORM对象,并使用save()方法将一个User对象保存到数据库表中。最后,需要调用close()方法关闭数据库连接。

6. 完整项目结构

至此,壹哥就带大家自定义了一个简单的ORM框架,完整的项目结构如下图所示:

实际上,我们这个ORM框架是比较简单的,大家可以根据壹哥的实现过程,再把修改、查询、删除等功能实现出来。

------------------------------正片已结束,来根事后烟----------------------------

四. 结语

本文重点给大家介绍了如何使用反射和注解来实现一个简单的ORM框架,并将其用于对象和数据库表的映射,现在你对ORM了解了吗?

虽然ORM框架可以大大简化数据库编程的复杂性,提高代码的灵活性和可扩展性。但是在实际开发中,我们要特别注意ORM框架的性能和可读性问题,避免过度使用反射和注解。另外如果你独自学习觉得有很多困难,可以加入壹哥的学习互助群,大家一起交流学习。

相关推荐
倔强的小石头_36 分钟前
【C语言指南】函数指针深度解析
java·c语言·算法
kangkang-4 小时前
PC端基于SpringBoot架构控制无人机(三):系统架构设计
java·架构·无人机
界面开发小八哥6 小时前
「Java EE开发指南」如何用MyEclipse创建一个WEB项目?(三)
java·ide·java-ee·myeclipse
ai小鬼头6 小时前
Ollama+OpenWeb最新版0.42+0.3.35一键安装教程,轻松搞定AI模型部署
后端·架构·github
idolyXyz6 小时前
[java: Cleaner]-一文述之
java
一碗谦谦粉7 小时前
Maven 依赖调解的两大原则
java·maven
萧曵 丶7 小时前
Rust 所有权系统:深入浅出指南
开发语言·后端·rust
netyeaxi7 小时前
Java:使用spring-boot + mybatis如何打印SQL日志?
java·spring·mybatis
收破烂的小熊猫~7 小时前
《Java修仙传:从凡胎到码帝》第四章:设计模式破万法
java·开发语言·设计模式
猴哥源码7 小时前
基于Java+SpringBoot的动物领养平台
java·spring boot