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的相关资源包的 首先安 ...
随机推荐
- POJ - 3045 Cow Acrobats (二分,或者贪心)
一开始是往二分上去想的,如果risk是x,题目要求则可以转化为一个不等式,Si + x >= sigma Wj ,j表示安排在i号牛上面的牛的编号. 如果考虑最下面的牛那么就可以写成 Si + ...
- 简析平衡树(三)——浅谈Splay
前言 原本以为\(Treap\)已经很难了,学习了\(Splay\),我才知道,没有最难,只有更难.(强烈建议先去学一学\(Treap\)再来看这篇博客) 简介 \(Splay\)是平衡树中的一种,除 ...
- 解决Win10桌面右键卡顿一直转圈圈的
把系统重置之后,发现在桌面点击右键时一直转圈,但是在文件夹等非桌面位置都正常.可能是我之前修改注册表添加右键选项造成的,也可能不是,因为将修改的地方删除还是没有解决问题,555. 上网搜素一波,发现大 ...
- response.setContentType("text/html;charset=utf-8")后依然乱码的解决方法
从浏览器获取数据到服务器,服务器将得到数据再显示在浏览器上英文字母正常显示,中文字符乱码的问题,已经使用了 response.setContentType("text/html;charse ...
- JDK和CGLIB动态代理原理区别
JDK和CGLIB动态代理原理区别 https://blog.csdn.net/yhl_jxy/article/details/80635012 2018年06月09日 18:34:17 阅读数:65 ...
- Tomcat启动xxx.keystore文件找不到
在server.xml里配置了 <Connector SSLEnabled="true" acceptCount="1000000" clientAuth ...
- TCP/IP与OSI参考模型原理
网络是很重要同时也是很难理解的知识,这篇文章将会用自己容易理解的方式来记录有关网络的tcp与osi模型内容,不求专业深刻,但求通俗易懂也好. OSI参考模型 OSI定义了网络互连的七层框架(物理层.数 ...
- django+xadmin在线教育平台(六)
4-1 使用py3.6和django1.11开发系统前注意事项 直接通过Python3.6和django最新版本来开发我们的系统的一些注意事项. 原版本: Python 2.7 & djang ...
- http 工作模式与模块
目录 http 工作模式与模块 http 服务器应用 MPM工作模式 prefork worker event 进程角色 httpd功能特性 http 安装 centos6配置目录 http 2.2 ...
- LeetCode954二倍数对数组
问题:二倍数对数组 给定一个长度为偶数的整数数组 A,只有对 A 进行重组后可以满足 “对于每个 0 <= i < len(A) / 2,都有 A[2 * i + 1] = 2 * A[2 ...