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 注入的例子,应该有更好的理解。
JDBC 学习笔记(十)—— 使用 JDBC 搭建一个简易的 ORM 框架的更多相关文章
- JDBC 学习笔记(六)—— PreparedStatement
1. 引入 PreparedStatement PreparedStatement 通过 Connection.createPreparedStatement(String sql) 方法创建,主要用 ...
- JDBC 学习笔记(十一)—— JDBC 的事务支持
1. 事务 在关系型数据库中,有一个很重要的概念,叫做事务(Transaction).它具有 ACID 四个特性: A(Atomicity):原子性,一个事务是一个不可分割的工作单位,事务中包括的诸操 ...
- JDBC学习笔记二
JDBC学习笔记二 4.execute()方法执行SQL语句 execute几乎可以执行任何SQL语句,当execute执行过SQL语句之后会返回一个布尔类型的值,代表是否返回了ResultSet对象 ...
- JDBC学习笔记一
JDBC学习笔记一 JDBC全称 Java Database Connectivity,即数据库连接,它是一种可以执行SQL语句的Java API. ODBC全称 Open Database Conn ...
- python3.4学习笔记(十四) 网络爬虫实例代码,抓取新浪爱彩双色球开奖数据实例
python3.4学习笔记(十四) 网络爬虫实例代码,抓取新浪爱彩双色球开奖数据实例 新浪爱彩双色球开奖数据URL:http://zst.aicai.com/ssq/openInfo/ 最终输出结果格 ...
- Hadoop学习笔记(3)——分布式环境搭建
Hadoop学习笔记(3) ——分布式环境搭建 前面,我们已经在单机上把Hadoop运行起来了,但我们知道Hadoop支持分布式的,而它的优点就是在分布上突出的,所以我们得搭个环境模拟一下. 在这里, ...
- python3.4学习笔记(十八) pycharm 安装使用、注册码、显示行号和字体大小等常用设置
python3.4学习笔记(十八) pycharm 安装使用.注册码.显示行号和字体大小等常用设置Download JetBrains Python IDE :: PyCharmhttp://www. ...
- Nutch1.7学习笔记:基本环境搭建及使用
Nutch1.7学习笔记:基本环境搭建及使用 作者:雨水,时间:2013-10-31博客地址:http://blog.csdn.net/gobitan 说明:Nutch有两个主版本1.x和2.x,它们 ...
- python3.4学习笔记(十六) windows下面安装easy_install和pip教程
python3.4学习笔记(十六) windows下面安装easy_install和pip教程 easy_install和pip都是用来下载安装Python一个公共资源库PyPI的相关资源包的 首先安 ...
随机推荐
- iOS开发学习之大牛们的博客
http://blog.csdn.net/iosbird/article/details/51981023 唐巧:http://blog.devtang.com/blog/archives/ 王巍:h ...
- kubernetes-配置管理(十一)
Secret https://kubernetes.io/docs/concepts/configuration/secret/ Secret解决了密码.token.密钥等敏感数据的配置问题,而不需要 ...
- 面向对象编程 -------JavaScrip
本文摘要:http://www.liaoxuefeng.com/ 一定明白面向对象的两个基本概念: 类:类是对象的类型模板,例如,定义Student类来表示学生,类本身是一种类型,Student表示学 ...
- linux下避免僵尸进程的几种方法
linux下我们可以调用fork函数创建子进程,创建的子进程将会得到父进程的数据空间.堆.栈......副本(采用写时复制机制),子进程将会继承父进程的信号掩码.信号处理方式.当前工作目录.会话id. ...
- 【Django】Django开发中的日志输出
开发环境:Ubuntu16.04+Django 1.11.9+Python2.7 一:使用自定义函数输出日志到log文件: import time def print_log(log): file_o ...
- 二十五、MySQL 索引
MySQL 索引 MySQL索引的建立对于MySQL的高效运行是很重要的,索引可以大大提高MySQL的检索速度. 打个比方,如果合理的设计且使用索引的MySQL是一辆兰博基尼的话,那么没有设计和使用索 ...
- Linux-git安装
基本操作 安装yum install git 生成SSH KEY :先cd ~/.ssh,在这个目录下输入ssh-keygen,一直回车就可以了,这个时候就会出现id_rsd.pub公钥和id_rsa ...
- Python知识点入门笔记——基本控制流程
复合赋值语句 在Python中,可以使用一次赋值符号,给多个变量同时赋值: 划重点:age_1,age_2 = age_2,age_1这种操作是Python独有的 i ...
- [Poj1273]Drainage Ditches(网络流)
Description 给图,求最大流 最大流模板题,这里用dinic Code #include <cstdio> #include <cstring> #include & ...
- CentOS下的Redis启动脚本
这是一个Shell脚本,用于管理Redis进程(启动,停止,重启),如果你在使用Redis,这个脚本可供参考. #!/bin/sh # # redis - this script starts and ...