zoukankan      html  css  js  c++  java
  • JDBC 学习笔记(十)—— 使用 JDBC 搭建一个简易的 ORM 框架

    1. 数据映射

    当我们获取到 ResultSet 之后,显然这个不是我们想要的数据结构。

    数据库中的每一个表,在 Java 代码中,一定会有一个类与之对应,例如:

    package com.gerrard.entity;
    
    import com.gerrard.annotation.ColumnAnnotation;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public final class Student {
    
        private int id;
    
        private String name;
    
        private String password;
    }
    

    实现数据库表和 JavaBean 之间的转换,就是 ORM(Object Relational Mapping)框架设计的目的。

    为此,我定义了一个转换的接口:

    package com.gerrard.orm;
    
    import java.sql.ResultSet;
    import java.sql.ResultSetMetaData;
    
    public interface ResultSetAdapter<T> {
    
        T transferEntity(ResultSet rs, ResultSetMetaData meta);
    }
    

    2. 死办法(这小章节不知道起什么名字好)

    最先想到的,无疑就是特事特办,为每一个 JavaBean 都写一个转换类:

    package com.gerrard.orm;
    
    import com.gerrard.constants.ErrorCode;
    import com.gerrard.entity.Student;
    import com.gerrard.exception.JdbcSampleException;
    
    import java.sql.ResultSet;
    import java.sql.ResultSetMetaData;
    import java.sql.SQLException;
    
    public final class StudentResultSetAdapter implements ResultSetAdapter<Student> {
    
        @Override
        public Student transferEntity(ResultSet rs, ResultSetMetaData meta) {
            try {
                int id = rs.getInt("STUDENT_ID");
                String name = rs.getString("STUDENT_NAME");
                String password = rs.getString("STUDENT_PASSWORD");
                return new Student(id, name, password);
            } catch (SQLException e) {
                throw new JdbcSampleException(ErrorCode.MISSING_COLUMN_ERROR, "Fail to find column.");
            }
        }
    }

    显然,这种做法对单一类很方便,但是 JavaBean 一旦增多,就会显得很冗余。

    3. 反射 + 注解

    观察例如 Hibernate 之类的实现,不难发现,JavaBean 的每一个与数据库列相对应的属性,都有一个 @Column 注解。

    那么,我们也可以使用类似的办法。

    第一步,定义一个注解。

    package com.gerrard.annotation;
    
    import java.lang.annotation.*;
    
    @Documented
    @Inherited
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface ColumnAnnotation {
    
        String column() default "";
    }
    

    第二步,将注解加到 JavaBean 中。

    package com.gerrard.entity;
    
    import com.gerrard.annotation.ColumnAnnotation;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public final class Student {
    
        @ColumnAnnotation(column = "STUDENT_ID")
        private int id;
    
        @ColumnAnnotation(column = "STUDENT_NAME")
        private String name;
    
        @ColumnAnnotation(column = "STUDENT_PASSWORD")
        private String password;
    }
    

    第三步,在创建转换类的时候,完成数据库列名-JavaBean 属性的映射关系的初始化。

    第四步,对 ResultSetMetaData 分析时,使用反射,将值注入到对应的 Field 中。

    package com.gerrard.orm;
    
    import com.gerrard.annotation.ColumnAnnotation;
    import com.gerrard.constants.ErrorCode;
    import com.gerrard.exception.JdbcSampleException;
    
    import java.lang.reflect.Field;
    import java.sql.ResultSet;
    import java.sql.ResultSetMetaData;
    import java.util.HashMap;
    import java.util.Map;
    
    public final class FlexibleResultSetAdapter<T> implements ResultSetAdapter<T> {
    
        private Map<String, Field> columnMap = new HashMap<>();
    
        private Class<T> clazz;
    
        public FlexibleResultSetAdapter(Class<T> clazz) {
            this.clazz = clazz;
            initColumnMap(clazz);
        }
    
        private void initColumnMap(Class<T> clazz) {
            for (Field field : clazz.getDeclaredFields()) {
                ColumnAnnotation annotation = field.getAnnotation(ColumnAnnotation.class);
                columnMap.put(annotation.column(), field);
            }
        }
    
        @Override
        public T transferEntity(ResultSet rs, ResultSetMetaData meta) {
            try {
                T t = clazz.newInstance();
                for (int i = 1; i <= meta.getColumnCount(); ++i) {
                    String dbColumn = meta.getColumnName(i);
                    Field field = columnMap.get(dbColumn);
                    if (field == null) {
                        throw new JdbcSampleException(ErrorCode.MISSING_COLUMN_ERROR, "Fail to find column " + dbColumn + ".");
                    }
                    field.setAccessible(true);
                    field.set(t, rs.getObject(i));
                }
                return t;
            } catch (Exception e) {
                String msg = "Fail to get ORM relation for class: " + clazz.getName();
                throw new JdbcSampleException(ErrorCode.MISSING_COLUMN_ERROR, msg);
            }
        }
    }
    

    最后,对 ORM 进行封装。

    package com.gerrard.executor;
    
    import com.gerrard.constants.ErrorCode;
    import com.gerrard.exception.JdbcSampleException;
    import com.gerrard.orm.ResultSetAdapter;
    import com.gerrard.util.Connector;
    import com.gerrard.util.DriverLoader;
    import lombok.AllArgsConstructor;
    import lombok.NoArgsConstructor;
    
    import java.sql.Connection;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.util.LinkedList;
    import java.util.List;
    
    @NoArgsConstructor
    @AllArgsConstructor
    public final class SqlExecutorStatement<T> implements SqlExecutor<T> {
    
        private ResultSetAdapter<T> adapter;
    
        @Override
        public int executeUpdate(String sql) {
            DriverLoader.loadSqliteDriver();
            try (Connection conn = Connector.getSqlConnection();
                 Statement stmt = conn.createStatement()) {
                return stmt.executeUpdate(sql);
            } catch (SQLException e) {
                String msg = "Fail to execute query using statement.";
                throw new JdbcSampleException(ErrorCode.EXECUTE_UPDATE_FAILURE, msg);
            }
        }
    
        @Override
        public List<T> executeQuery(String sql) {
            DriverLoader.loadSqliteDriver();
            try (Connection conn = Connector.getSqlConnection();
                 Statement stmt = conn.createStatement()) {
                List<T> list = new LinkedList<>();
                try (ResultSet rs = stmt.executeQuery(sql)) {
                    while (rs.next()) {
                        list.add(adapter.transferEntity(rs, rs.getMetaData()));
                    }
                }
                return list;
            } catch (SQLException e) {
                String msg = "Fail to execute query using statement.";
                throw new JdbcSampleException(ErrorCode.EXECUTE_QUERY_FAILURE, msg);
            }
        }
    }
    

    这样一来,对于 JDBC 学习笔记(六)—— PreparedStatement 中 SQL 注入的例子,应该有更好的理解。

  • 相关阅读:
    解决在Apple Silicon (M1)安装php MongoDB扩展失败
    dyld: Library not loaded: /usr/local/opt/libpng/lib/libpng16.16.dylib
    docker自建bitwarden_rs服务器更新支持send功能
    centos安装puppeteer遇到的报错及解决方案
    Centos宝塔安装NextCloud
    苹果设备型号代码(更新至iPhone12)
    electron内使用vue-slider-component组件报“$attrs is readonly”错误
    ZSH隐藏命令行前面的用户名和主机名
    Android9.0配置charles的https抓包
    记一次discuz修改首页图片路径问题
  • 原文地址:https://www.cnblogs.com/jing-an-feng-shao/p/9227130.html
Copyright © 2011-2022 走看看