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的相关资源包的 首先安 ...
随机推荐
- 使用POI 读取 Excel 文件,读取手机号码 变成 1.3471022771E10
使用POI 读取 Excel 文件,读取手机号码 变成 1.3471022771E10 [问题点数:40分,结帖人xieyongqiu] 不显示删除回复 ...
- iOS 制作表格 (数据源控制行,列数)
记得去年面试的过程中,有一个面试官问我怎么制作表格.由于之前也没有做过,当时有点懵逼,今天想起来了,就用tableview制作了一个,望不要有人像我一样掉坑了, 直接上代码: // // ViewCo ...
- Problem C: 动态规划基础题目之数字三角形
Problem C: 动态规划基础题目之数字三角形 Time Limit: 1 Sec Memory Limit: 64 MBSubmit: 208 Solved: 139[Submit][Sta ...
- python剑指offer 顺时针打印指针
题目描述 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数 ...
- kubernetes-服务发现service(九)
service •防止Pod失联 •定义一组Pod的访问策略 •支持ClusterIP,NodePort以及LoadBalancer三种类型 •Service的底层实现主要有ipta ...
- cuda流测试=basic_single_stream
cuda流测试 /* * Copyright 1993-2010 NVIDIA Corporation. All rights reserved. * * NVIDIA Corporation and ...
- 魅族MX3 Flyme3.0找回手机功能支持远程拍照密码错两次自动拍照
进入Flyme页面(http://app.meizu.com/),选择“查找手机”即可进行查找自己登记的魅族系列手机. 如果您在一个账号下登记过N多魅族系列手机,那么都是可以进行查找的,参见下图 魅族 ...
- 深入理解java虚拟机读书笔记1--java内存区域
Java在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途.创建和销毁的时间,有一些是随虚拟机的启动而创建,随虚拟机的退出而销毁,有些则是与线程一一对应,随 ...
- grep与正则表达式使用
grep简介 grep 是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来.通常grep有三种版本grep.egrep(等同于grep -E)和fgrep.egrep为扩展的g ...
- 微信小游戏 demo 飞机大战 代码分析 (三)(spirit.js, animation.js)
微信小游戏 demo 飞机大战 代码分析(三)(spirit.js, animation.js) 微信小游戏 demo 飞机大战 代码分析(一)(main.js) 微信小游戏 demo 飞机大战 代码 ...