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