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. 使用POI 读取 Excel 文件,读取手机号码 变成 1.3471022771E10

    使用POI 读取 Excel 文件,读取手机号码 变成 1.3471022771E10 [问题点数:40分,结帖人xieyongqiu]             不显示删除回复             ...

  2. iOS 制作表格 (数据源控制行,列数)

    记得去年面试的过程中,有一个面试官问我怎么制作表格.由于之前也没有做过,当时有点懵逼,今天想起来了,就用tableview制作了一个,望不要有人像我一样掉坑了, 直接上代码: // // ViewCo ...

  3. Problem C: 动态规划基础题目之数字三角形

    Problem C: 动态规划基础题目之数字三角形 Time Limit: 1 Sec  Memory Limit: 64 MBSubmit: 208  Solved: 139[Submit][Sta ...

  4. python剑指offer 顺时针打印指针

    题目描述 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数 ...

  5. kubernetes-服务发现service(九)

    service •防止Pod失联    •定义一组Pod的访问策略    •支持ClusterIP,NodePort以及LoadBalancer三种类型    •Service的底层实现主要有ipta ...

  6. cuda流测试=basic_single_stream

    cuda流测试 /* * Copyright 1993-2010 NVIDIA Corporation. All rights reserved. * * NVIDIA Corporation and ...

  7. 魅族MX3 Flyme3.0找回手机功能支持远程拍照密码错两次自动拍照

    进入Flyme页面(http://app.meizu.com/),选择“查找手机”即可进行查找自己登记的魅族系列手机. 如果您在一个账号下登记过N多魅族系列手机,那么都是可以进行查找的,参见下图 魅族 ...

  8. 深入理解java虚拟机读书笔记1--java内存区域

    Java在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途.创建和销毁的时间,有一些是随虚拟机的启动而创建,随虚拟机的退出而销毁,有些则是与线程一一对应,随 ...

  9. grep与正则表达式使用

    grep简介 grep 是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来.通常grep有三种版本grep.egrep(等同于grep -E)和fgrep.egrep为扩展的g ...

  10. 微信小游戏 demo 飞机大战 代码分析 (三)(spirit.js, animation.js)

    微信小游戏 demo 飞机大战 代码分析(三)(spirit.js, animation.js) 微信小游戏 demo 飞机大战 代码分析(一)(main.js) 微信小游戏 demo 飞机大战 代码 ...