JDBC 学习笔记(十)—— 使用 JDBC 搭建一个简易的 ORM 框架 JDBC 学习笔记(六)—— PreparedStatement

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 注入的例子,应该有更好的理解。

猜你喜欢

转载自www.cnblogs.com/jing-an-feng-shao/p/9227130.html