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 框架的更多相关文章

  1. JDBC 学习笔记(六)—— PreparedStatement

    1. 引入 PreparedStatement PreparedStatement 通过 Connection.createPreparedStatement(String sql) 方法创建,主要用 ...

  2. JDBC 学习笔记(十一)—— JDBC 的事务支持

    1. 事务 在关系型数据库中,有一个很重要的概念,叫做事务(Transaction).它具有 ACID 四个特性: A(Atomicity):原子性,一个事务是一个不可分割的工作单位,事务中包括的诸操 ...

  3. JDBC学习笔记二

    JDBC学习笔记二 4.execute()方法执行SQL语句 execute几乎可以执行任何SQL语句,当execute执行过SQL语句之后会返回一个布尔类型的值,代表是否返回了ResultSet对象 ...

  4. JDBC学习笔记一

    JDBC学习笔记一 JDBC全称 Java Database Connectivity,即数据库连接,它是一种可以执行SQL语句的Java API. ODBC全称 Open Database Conn ...

  5. python3.4学习笔记(十四) 网络爬虫实例代码,抓取新浪爱彩双色球开奖数据实例

    python3.4学习笔记(十四) 网络爬虫实例代码,抓取新浪爱彩双色球开奖数据实例 新浪爱彩双色球开奖数据URL:http://zst.aicai.com/ssq/openInfo/ 最终输出结果格 ...

  6. Hadoop学习笔记(3)——分布式环境搭建

    Hadoop学习笔记(3) ——分布式环境搭建 前面,我们已经在单机上把Hadoop运行起来了,但我们知道Hadoop支持分布式的,而它的优点就是在分布上突出的,所以我们得搭个环境模拟一下. 在这里, ...

  7. python3.4学习笔记(十八) pycharm 安装使用、注册码、显示行号和字体大小等常用设置

    python3.4学习笔记(十八) pycharm 安装使用.注册码.显示行号和字体大小等常用设置Download JetBrains Python IDE :: PyCharmhttp://www. ...

  8. Nutch1.7学习笔记:基本环境搭建及使用

    Nutch1.7学习笔记:基本环境搭建及使用 作者:雨水,时间:2013-10-31博客地址:http://blog.csdn.net/gobitan 说明:Nutch有两个主版本1.x和2.x,它们 ...

  9. python3.4学习笔记(十六) windows下面安装easy_install和pip教程

    python3.4学习笔记(十六) windows下面安装easy_install和pip教程 easy_install和pip都是用来下载安装Python一个公共资源库PyPI的相关资源包的 首先安 ...

随机推荐

  1. IOS 截屏(保存到相册中)

    @interface NJViewController () /** * 点击截屏按钮 */ - (IBAction)captureView:(UIButton *)sender; /** * 白色v ...

  2. AOJ 558 Cheese(bfs)

    题意:网格图,老鼠吃奶酪,吃完奶酪体力值会增加,只能吃硬度不大于体力值的,问最小步数. 思路:按硬度从小到大的吃起,依次求最短路. 我用曼哈顿距离估价的A*,和普通bfs的time没区别啊,还把优先级 ...

  3. bzoj4393: [Usaco2015 Dec]Fruit Feast

    题意: T,A,B.T是上限.A和B可以随意吃但是不能超过T.有一次将吃的东西/2的机会.然后可以继续吃,不能超过T.问最多可以吃多少. =>我们先处理不能/2可以吃到哪些.然后弄个双指针扫一扫 ...

  4. POJ2112 Optimal Milking---二分+Floyd+网络流

    题目链接: https://vjudge.net/problem/POJ-2112 题目大意: k个机器,每个机器最多服务m头牛. c头牛,每个牛需要1台机器来服务. 告诉你牛与机器每个之间的直接距离 ...

  5. MFC-[转]基于MFC的ActiveX控件开发

    作者:lidan | 出处:博客园 | 2012/3/13 16:10:34 | 阅读22次 ActiveX 控件是基于组件对象模型 (COM) 的可重用软件组件,广泛应用于桌面及Web应用中.在VC ...

  6. Ubuntu 上配置静态的ip

    先关掉或卸掉 network-manager.然后,改动/etc/network/interfaces 如下:(由于是静态ip,你当然知道把例子中那些东西改成你自己的)auto lo eth0ifac ...

  7. React后台管理系统-用户列表页面

    1.页面的结构 //遍历list, 返回数据       let listBody= this.state.list.map((user,index)=> {           return ...

  8. 关于SQL数据库 msdb.dbo.sp_send_dbmail 函数发送邮件的场景分析

    关于SQL数据库 msdb.dbo.sp_send_dbmail 函数发送邮件的场景分析 在推行系统中,时不时会有用户提出希望系统能自动推送邮件,由于手头的工具和能力有限,不少需求都借助于sql se ...

  9. SummerVocation_Learning--java的String类运用

    题目: 编写一个程序,输出一个字符串中的大写字母数,小写字母数,及其它字母数. 思路1: 可以先遍历整个字符串,在判断每个字符的类型. public class TestString { public ...

  10. 基于Ajax提交formdata数据、错误信息展示和局部钩子、全局钩子的校验。

    formdata重点: 实例化FormData这个类 循环serializeArray可以节省代码量 图片要用$('#id')[0].files[0]来获得 加上contentType:false和p ...