orm框架设计、分析与开发
前面写过几篇文章介绍和分析mybatis,今天拆解下要设计一个ORM框架涉及到哪些方面,如何用现有的一些已知工具像spring jdbc、freemarker等重新造一个ORM框架出来,整体的拆解结构如图所示。
该ORM框架源码有兴趣的可以评论区留言备注下邮箱找我要下,因为是个人写的,所以不可能面面俱到,但是ORM框架基本的功能具备,使用也非常方便,会freemarker语法的话一看就会用,使用起来感觉应该比mybatis更容易。
一、准备阶段
准备阶段是指sql操作前,例如容器启动的时候需要做的一些事情,主要包括两个部分
1、解析映射
将sql xml中sqlid和sql操作语句之间的关联关系建立起来,后面sql操作的时候就可以直接根据sqlid 找到对应的sql语句
2、数据源映射
将数据库和数据源之间的关联关系建立起来,后面指定某个库时就可以直接找到对应的数据源来获取连接。
二、执行阶段
执行阶段就是真正的sql执行了,原生的jdbc sql操作主要包括几个部分,驱动加载、设置url用户名和密码、获取数据库连接,创建statement、执行sql操作、结果集处理,最后关闭。
1、sql绑定
准备阶段中保存的sql语句是带有一些特殊标记的像if、for之类的标签,需要根据入参的值来处理sql将这些标签去除,freemarker模板框架就能够满足这个要求。
2、数据库连接池
可以选c3p0、druid、dbcp
3、sql操作工具
选择spring jdbc template来支持
4、对象映射
结果集转换成对应的类型对象,或者对象装换成参数Map,可以通过commons-beanutils实现
三、使用示例
因为是在spring jdbc的基础上开发的orm框架,所以采用spring xml配置的方式进行配置
1、单数据源实例
spes-da-default.xml 配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--直接配置数据库连接-->
<bean id="testDbDs" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!-- DAL客户端接口实现 -->
<bean id="sqlSession"
class="com.handserome.daat.session.DefaultSqlSession">
<!--这个地方设置sql map文件的路径-->
<property name="sqlMapConfigLocation" value="classpath*:conf/sqlMap/sqlMap_*.xml"/>
<property name="dataSource" ref="testDbDs"/>
</bean>
</beans>
sql map映射文件:包含增删改查的示例
<?xml version="1.0" encoding="UTF-8" ?>
<sqlMap namespace="student" jdbcTimeout="60">
<!--查询 -->
<sql id="queryStudentById">
<![CDATA[
SELECT *
FROM ${tableName}
WHERE id = :id
<#if name?? && name != "">
AND name = :name
</#if>
]]>
</sql>
<!-- 根据id批量查询 -->
<sql id="queryStudentByIds">
<![CDATA[
SELECT *
FROM ${tableName}
WHERE id in (
<#list idList as ids>
:id${ids_index}
<#if ids_has_next>,</#if>
</#list>
)
]]>
</sql>
<!-- 删除 -->
<sql id="deleteData">
<![CDATA[
DELETE FROM student
WHERE name = :name
]]>
</sql>
<!-- 保存 -->
<sql id="saveData">
<![CDATA[
INSERT INTO student
(student_id, name)
VALUES
(:studentId,:name)
]]>
</sql>
<!-- 更新 -->
<sql id="batchUpdate">
<![CDATA[
update student
set name = :name
<#if createTime?? && createTime != "">
,create_time = :createTime
</#if>
where student_id = :studentId
]]>
</sql>
</sqlMap>
数据库操作实例
package com.handserome.test;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.handserome.daat.session.SqlSession;
import com.handserome.test.entity.Student;
/**
* Hello world!
*
*/
public class SqlSessionTest {
private static SqlSession sqlSession;
private static SqlSession sqlSession1;
public static void main(String[] args) {
// 默认,非分库
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:daat-da-default.xml");
sqlSession = (SqlSession) context.getBean("sqlSession");
// 可以灵活指定查询的数据库分库
ClassPathXmlApplicationContext context1 = new ClassPathXmlApplicationContext("classpath:daat-da-shard.xml");
sqlSession1 = (SqlSession) context1.getBean("sqlSession");
// 默认非分库测试
queryForObj();
queryForList();
delete();
insert();
insert1();
batchUpdateTest();
// 分库测试
queryForObjShard();
queryForListShard();
deleteShard();
insertShard();
insertShard1();
batchUpdateShardTest();
}
/**
*
* 功能描述: 查询对象
*
* @param
* @return
* @see [相关类/方法](可选)
* @since [产品/模块版本](可选)
*/
private static void queryForObj() {
String sqlId1 = "student.queryStudentById";
Map<String, Object> paramMap1 = new HashMap<String, Object>();
paramMap1.put("id", 1);
// paramMap.put("name", "111");
paramMap1.put("tableName", "student");
Student student1 = sqlSession.queryForObject(sqlId1, paramMap1, Student.class);
System.out.println(student1.toString());
}
/**
*
* 功能描述: 批量查询
*
* @param
* @return
* @see [相关类/方法](可选)
* @since [产品/模块版本](可选)
*/
private static void queryForList() {
Map<String, Object> paramMap = new HashMap<String, Object>();
List<Integer> idList = new ArrayList<Integer>();
for (int i = 0; i < 4; i++) {
paramMap.put("id" + i, i);
idList.add(i);
}
paramMap.put("idList", idList);
paramMap.put("tableName", "student");
String sqlId = "student.queryStudentByIds";
List<Student> studentList = sqlSession.queryForList(sqlId, paramMap, Student.class);
for (Student student : studentList) {
System.out.println("根据id批量查询" + student);
}
}
/**
*
* 功能描述: 删除
*
* @param
* @return
* @see [相关类/方法](可选)
* @since [产品/模块版本](可选)
*/
private static void delete() {
Map<String, Object> paramMap12 = new HashMap<String, Object>();
paramMap12.put("name", "aaa");
String deleteData = "student.deleteData";
int execute = sqlSession.execute(deleteData, paramMap12);
System.out.println("删除条数" + execute);
}
/**
* 插入
*/
private static void insert() {
Map<String, Object> paramMap11 = new HashMap<String, Object>();
paramMap11.put("studentId", "222");
paramMap11.put("name", "bbb");
String insert = "student.saveData";
int execute = sqlSession.execute(insert, paramMap11);
System.out.println("插入条数" + execute);
}
/**
*
* 功能描述: 批量插入
*
* @param
* @return
* @see [相关类/方法](可选)
* @since [产品/模块版本](可选)
*/
private static void insert1() {
List<Object> objectList = new ArrayList<Object>();
Student student = new Student();
student.setStudentId("7878");
student.setName("7878");
student.setCreateTime(new Timestamp(new java.util.Date().getTime()));
objectList.add(student);
Student student1 = new Student();
student1.setStudentId("78781");
student1.setName("78781");
student1.setCreateTime(new Timestamp(new java.util.Date().getTime()));
objectList.add(student1);
String insert = "student.saveData";
int i = sqlSession.batchUpdate(insert, objectList);
System.out.println("插入条数" + i);
}
/**
*
* 功能描述: 批量更新
*
* @param
* @return
* @see [相关类/方法](可选)
* @since [产品/模块版本](可选)
*/
private static void batchUpdateTest() {
List<Object> studentList = new ArrayList<Object>();
for (int i = 0; i < 5; i++) {
Student student = new Student();
student.setStudentId("" + i);
student.setName("name" + i);
if ((i % 2) ==0) {
student.setCreateTime(new Timestamp(new java.util.Date().getTime()));
}
studentList.add(student);
}
String sqlId = "student.batchUpdate";
int i = sqlSession.batchUpdate(sqlId, studentList);
System.out.println("批量更新条数" + i);
}
/**
*
* 功能描述: 查询对象
*
* @param
* @return
* @see [相关类/方法](可选)
* @since [产品/模块版本](可选)
*/
private static void queryForObjShard() {
String sqlId1 = "student.queryStudentById";
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("id", 1);
// paramMap.put("name", "111");
paramMap.put("tableName", "student");
// 指定数据库
paramMap.put("dbKey", "db1");
Student student1 = sqlSession1.queryForObject(sqlId1, paramMap, Student.class);
System.out.println(student1.toString());
}
/**
*
* 功能描述: 批量查询
*
* @param
* @return
* @see [相关类/方法](可选)
* @since [产品/模块版本](可选)
*/
private static void queryForListShard() {
Map<String, Object> paramMap = new HashMap<String, Object>();
List<Integer> idList = new ArrayList<Integer>();
for (int i = 0; i < 4; i++) {
paramMap.put("id" + i, i);
idList.add(i);
}
paramMap.put("idList", idList);
paramMap.put("tableName", "student");
// 指定数据库
paramMap.put("dbKey", "db1");
String sqlId = "student.queryStudentByIds";
List<Student> studentList = sqlSession1.queryForList(sqlId, paramMap, Student.class);
for (Student student : studentList) {
System.out.println("根据id批量查询" + student);
}
}
/**
*
* 功能描述: 删除
*
* @param
* @return
* @see [相关类/方法](可选)
* @since [产品/模块版本](可选)
*/
private static void deleteShard() {
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("name", "aaa");
// 指定数据库
paramMap.put("dbKey", "db1");
String deleteData = "student.deleteData";
int execute = sqlSession1.execute(deleteData, paramMap);
System.out.println("删除条数" + execute);
}
/**
* 插入
*/
private static void insertShard() {
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("studentId", "222");
paramMap.put("name", "bbb");
paramMap.put("dbKey", "db1");
String insert = "student.saveData";
int execute = sqlSession1.execute(insert, paramMap);
System.out.println("插入条数" + execute);
}
/**
*
* 功能描述: 批量插入
*
* @param
* @return
* @see [相关类/方法](可选)
* @since [产品/模块版本](可选)
*/
private static void insertShard1() {
List<Object> objectList = new ArrayList<Object>();
Student student = new Student();
student.setStudentId("7878");
student.setName("7878");
student.setCreateTime(new Timestamp(new java.util.Date().getTime()));
student.setDbKey("db1");
objectList.add(student);
Student student1 = new Student();
student1.setStudentId("78781");
student1.setName("78781");
student1.setDbKey("db1");
student1.setCreateTime(new Timestamp(new java.util.Date().getTime()));
objectList.add(student1);
String insert = "student.saveData";
int i = sqlSession1.batchUpdate(insert, objectList);
System.out.println("插入条数" + i);
}
/**
*
* 功能描述: 批量更新
*
* @param
* @return
* @see [相关类/方法](可选)
* @since [产品/模块版本](可选)
*/
private static void batchUpdateShardTest() {
List<Object> studentList = new ArrayList<Object>();
for (int i = 0; i < 5; i++) {
Student student = new Student();
student.setStudentId("" + i);
student.setName("name" + i);
student.setDbKey("db1");
if ((i % 2) ==0) {
student.setCreateTime(new Timestamp(new java.util.Date().getTime()));
}
studentList.add(student);
}
String sqlId = "student.batchUpdate";
int i = sqlSession1.batchUpdate(sqlId, studentList);
System.out.println("批量更新条数" + i);
}
}
2、多数据源实例配置
多数据源配置类daat-da-shard.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--直接配置数据库连接-->
<bean id="testDbDs1" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!--直接配置数据库连接-->
<bean id="testDbDs2" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test1"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<bean id="shardRegister"
class="com.handserome.daat.datasource.DefaultDbShardRegister">
<property name="dbShardInfos">
<set>
<!-- 测试库1 -->
<bean class="com.handserome.daat.datasource.DbShardInfo">
<property name="id" value="db1"/>
<property name="dataSource" ref="testDbDs1"/>
<property name="description" value="测试1库数据源"/>
</bean>
<!-- 测试库2 -->
<bean class="com.handserome.daat.datasource.DbShardInfo">
<property name="id" value="db2"/>
<property name="dataSource" ref="testDbDs2"/>
<property name="description" value="测试1库数据源"/>
</bean>
</set>
</property>
</bean>
<!-- DAL客户端接口实现 -->
<bean id="sqlSession"
class="com.handserome.daat.session.DbShardSqlSession">
<property name="dbShardRegister" ref="shardRegister"/>
<property name="sqlMapConfigLocation" value="classpath*:conf/sqlMap/sqlMap_*.xml"/>
</bean>
</beans>
sql map映射文件和java测试类参考单数源实例
3、结果展示
数据库就涉及一个student表,默认支持字段和对像驼峰映射,应此也不用想mybatis一样写一堆resultType 映射。
CREATE TABLE `student` (
`student_id` int(11) DEFAULT NULL,
`name` varchar(255) CHARACTER SET utf8 DEFAULT NULL,
`id` int(11) NOT NULL AUTO_INCREMENT,
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
数据库操作正确
[id=1, studentId=1, name=name1, createTime=null]
根据id批量查询[id=1, studentId=1, name=name1, createTime=null]
根据id批量查询[id=2, studentId=222, name=bbb, createTime=null]
根据id批量查询[id=3, studentId=7878, name=7878, createTime=null]
删除条数0
插入条数1
插入条数2
批量更新条数1
[id=1, studentId=1, name=name1, createTime=null]
根据id批量查询[id=1, studentId=1, name=name1, createTime=null]
根据id批量查询[id=2, studentId=222, name=bbb, createTime=null]
根据id批量查询[id=3, studentId=7878, name=7878, createTime=null]
删除条数0
插入条数1
插入条数2
批量更新条数1