美文网首页
你真的了解ORM吗?通过一个简单的例子来学习ORM

你真的了解ORM吗?通过一个简单的例子来学习ORM

作者: 小虎哥的技术博客 | 来源:发表于2023-08-13 09:07 被阅读0次

    什么是ORM

    ORM(Object-Relational Mapping)是一种将面向对象程序数据模型与关系数据库之间进行映射的技术。

    比如数据库表user,它有id、name、age字段映射到Java实体类就是User类,有id、name、age属性。

    CREATE TABLE `user` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(20) COLLATE utf8mb4_general_ci DEFAULT NULL,
      `age` int(11) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
    
    @Entity
    @Table(name = "user")
    public class User {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private int id;
        private String name;
        private int age;
    
        // 省略setter和getter
    }
    

    什么是JPA

    JPA(Java Persistence API)(Java持久化接口)是Java平台提供的一套标准化的持久化框架,用于简化Java对象与数据库之间的交互。

    JPA帮你隐藏了底层数据库细节,你只需要操作对象,而不需要编写复杂的SQL语句,JPA会帮你自动生成相应的SQL语句并执行。

    其实JPA就是帮我定义好了一系列注解,它不提供具体的实现,只是定规范。如下:


    4a826915-0247-4a20-9741-f17faeafa280.png

    下面这些注解是否很熟悉

    @Documented
    @Target(TYPE)
    @Retention(RUNTIME)
    public @interface Entity {
    
        String name() default "";
    }
    
    
    public @interface OneToMany {
    
        Class targetEntity() default void.class;
    
        CascadeType[] cascade() default {};
    
        FetchType fetch() default LAZY;
    
        String mappedBy() default "";
    
        boolean orphanRemoval() default false;
    }
    
    @Target({METHOD, FIELD}) 
    @Retention(RUNTIME)
    public @interface Column {
    
        String name() default "";
    
        boolean unique() default false;
    
        boolean nullable() default true;
    
        boolean insertable() default true;
    
        boolean updatable() default true;
        
        String columnDefinition() default "";
        
        String table() default "";
    
        int length() default 255;
        
        int precision() default 0;
    
        int scale() default 0;
    }
    

    就是JPA只定义接口规范,具体怎么实现各个厂家自己去做,以下是基于JPA的常用的框架。

    • Hibernate
    • OpenJPA
    • Spring Data JPA

    自己封装一个ORM框架

    下面通过一个代码示例来自己封装一个简单的ORM框架。

    引入依赖包,就是mysql-connector-java和javax.persistence-api

    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>
    
        <dependency>
            <groupId>javax.persistence</groupId>
            <artifactId>javax.persistence-api</artifactId>
            <version>2.2</version>
        </dependency>
    </dependencies>
    

    如下是JDBC获取数据库连接

    import java.sql.Connection;
    import java.sql.DriverManager;
    
    public class ConnectionUtil {
    
        public static Connection getConnection() throws Exception {
            String url = "jdbc:mysql://localhost:3306/jdbc_orm?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false";
            String user = "root";
            String password = "123456";
            return DriverManager.getConnection(url, user, password);
        }
    }
    

    具体的ORM核心代码,用Java的反射技术来实现。

    import javax.persistence.Entity;
    import javax.persistence.Id;
    import java.lang.reflect.Field;
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.util.ArrayList;
    import java.util.List;
    
    public class OrmUtil {
    
        private final Connection connection;
    
        public OrmUtil(Connection connection) {
            this.connection = connection;
        }
    
        public <T> void save(T entity) throws Exception {
            // 获取实体对象的Class对象
            Class<?> clazz = entity.getClass();
            // 获取实体对象所对应的数据库表名
            String tableName = getTableName(clazz);
            // 存储实体对象的列名和对应的值
            List<String> columns = new ArrayList<>();
            List<Object> values = new ArrayList<>();
    
            // 获取实体对象的字段
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                // 设置字段可访问
                field.setAccessible(true);
                // 存储字段名
                columns.add(field.getName());
                // 存储字段值,由于是Object类型,需要强制类型转换
                values.add(field.get(entity));
            }
    
            // 构建SQL语句
            StringBuilder sqlBuilder = new StringBuilder();
            sqlBuilder.append("INSERT INTO ").append(tableName).append(" (");
            for (int i = 0; i < columns.size(); i++) {
                sqlBuilder.append(columns.get(i));
                if (i < columns.size() - 1) {
                    sqlBuilder.append(", ");
                }
            }
            sqlBuilder.append(") VALUES (");
            for (int i = 0; i < values.size(); i++) {
                sqlBuilder.append("?");
                if (i < values.size() - 1) {
                    sqlBuilder.append(", ");
                }
            }
            sqlBuilder.append(")");
            String sql = sqlBuilder.toString();
    
            // 打印SQL语句
            System.out.println("执行新增语句;" + sql);
    
            // 执行SQL语句
            try (PreparedStatement statement = connection.prepareStatement(sql)) {
                for (int i = 0; i < values.size(); i++) {
                    // 设置参数,由于参数类型不确定,使用setObject方法
                    statement.setObject(i + 1, values.get(i));
                }
                statement.executeUpdate();
            }
        }
    
        public <T> void update(T entity) throws Exception {
            // 获取实体对象的Class对象
            Class<?> clazz = entity.getClass();
            // 获取实体对象所对应的数据库表名
            String tableName = getTableName(clazz);
            // 存储实体对象的非主键列名和对应的值
            List<String> columns = new ArrayList<>();
            List<Object> values = new ArrayList<>();
            // 存储主键值
            Object idValue = null;
    
            // 获取实体对象的字段
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                // 设置字段可访问
                field.setAccessible(true);
                // 判断字段是否有@Id注解,若有,则将其值作为主键值存储
                if (field.isAnnotationPresent(Id.class)) {
                    idValue = field.get(entity);
                } else {
                    // 若没有@Id注解,则将其列名和值存储
                    columns.add(field.getName());
                    values.add(field.get(entity));
                }
            }
    
            // 构建SQL语句
            StringBuilder sqlBuilder = new StringBuilder();
            sqlBuilder.append("UPDATE ").append(tableName).append(" SET ");
            for (int i = 0; i < columns.size(); i++) {
                sqlBuilder.append(columns.get(i)).append(" = ?");
                if (i < columns.size() - 1) {
                    sqlBuilder.append(", ");
                }
            }
            sqlBuilder.append(" WHERE id = ?");
            String sql = sqlBuilder.toString();
    
            System.out.println("执行更新语句;" + sql);
    
            // 执行SQL语句
            try (PreparedStatement statement = connection.prepareStatement(sql)) {
                for (int i = 0; i < values.size(); i++) {
                    // 设置非主键列的参数值
                    statement.setObject(i + 1, values.get(i));
                }
                // 设置主键参数值
                statement.setObject(values.size() + 1, idValue);
                statement.executeUpdate();
            }
        }
    
        public void delete(Class<?> clazz, Object id) throws Exception {
            // 获取实体对象所对应的数据库表名
            String tableName = getTableName(clazz);
            // 构建SQL语句
            String sql = "DELETE FROM " + tableName + " WHERE id = ?";
            System.out.println("执行删除语句:" + sql);
    
            // 执行SQL语句
            try (PreparedStatement statement = connection.prepareStatement(sql)) {
                // 设置参数值
                statement.setObject(1, id);
                // 执行删除操作
                statement.executeUpdate();
            }
        }
    
        public <T> T findById(Class<T> clazz, Object id) throws Exception {
            // 获取实体对象所对应的数据库表名
            String tableName = getTableName(clazz);
            // 构建SQL语句
            String sql = "SELECT * FROM " + tableName + " WHERE id = ?";
    
            System.out.println("执行查询语句;" + sql);
    
            // 执行SQL查询
            try (PreparedStatement statement = connection.prepareStatement(sql)) {
                // 设置参数值
                statement.setObject(1, id);
                // 执行查询操作并获取结果集
                try (ResultSet resultSet = statement.executeQuery()) {
                    // 如果有结果,则创建实体对象并设置字段值
                    if (resultSet.next()) {
                        T entity = clazz.newInstance();
                        Field[] fields = clazz.getDeclaredFields();
                        for (Field field : fields) {
                            field.setAccessible(true);
                            // 根据字段名从结果集中获取对应的值,并设置到实体对象中
                            Object value = resultSet.getObject(field.getName());
                            field.set(entity, value);
                        }
                        return entity;
                    }
                }
            }
            return null;
        }
    
        private String getTableName(Class<?> clazz) {
            // 判断类是否有@Entity注解
            if (clazz.isAnnotationPresent(Entity.class)) {
                // 获取类上的@Entity注解对象
                Entity entityAnnotation = clazz.getAnnotation(Entity.class);
                // 获取注解中的表名
                String tableName = entityAnnotation.name();
                // 如果注解中的表名为空,则将类名转换为小写作为表名
                if (tableName.isEmpty()) {
                    tableName = clazz.getSimpleName().toLowerCase();
                }
                return tableName;
            }
            // 如果类没有@Entity注解,抛出异常
            throw new IllegalArgumentException("类没有@Entity注解");
        }
    }
    

    这段代码是一个简单的ORM(对象关系映射)工具类,可以通过调用其提供的save、update、delete、findById等方法,实现对数据库表的增、删、改、查操作。

    在这段代码中主要包括以下内容:

    1. 通过反射获取类的属性、方法和注解等信息;

    2. 通过PreparedStatement实现对数据库的增、删、改、查等操作;

    3. 对Java泛型机制的运用,如在save、update、findById等方法中,将类名和主键值等作为方法参数,以达到通用的效果;

    4. 对JPA注解的运用,如对@Id、@Entity等注解的解析、获取注解值等操作。


    测试增删改查

    import java.sql.Connection;
    
    public class Test {
    
        public static void main(String[] args) {
            try {
                // 获取数据库连接
                Connection connection = ConnectionUtil.getConnection();
    
                // 创建一个实体管理器
                OrmUtil ormUtil = new OrmUtil(connection);
    
                // 创建一个User对象
                User user = new User();
                user.setId(1);
                user.setName("张三");
                user.setAge(28);
    
                // 保存User对象到数据库
                ormUtil.save(user);
    
                // 修改User对象
                user.setAge(30);
                ormUtil.update(user);
    
                // 根据id查询User对象
                User foundUser = ormUtil.findById(User.class, 1);
                System.out.println("查询结果:"+foundUser); // 输出:"张三"
    
                // 删除User对象
                ormUtil.delete(User.class, 1);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    结语

    以上只是最简单的增删改查,实际的ORM框架如Hibernate的原理都是差不多的,只是提供的增删改查的接口更多更丰富。还是要对Java的反射运用要深入。

    相关文章

      网友评论

          本文标题:你真的了解ORM吗?通过一个简单的例子来学习ORM

          本文链接:https://www.haomeiwen.com/subject/lcufmdtx.html