一.ORM 思想
目前,通过 Java 语言连接并操作数据库的技术或方式已经有很多了,例如:JDBC,
Hibernate,MyBatis,TopLink 等等。其中 JDBC 是 Java 原生的 API,支持连接并操作各种关系型数据库。相信每个程序员都是从 JDBC 开始学起的,然后才接触到各种持久层框架。
JDBC 作为 Java 原生 API,有优点,也有缺点,这里主要说一下缺点:
- 编码繁琐,效率低
- 数据库连接的创建和释放比较重复,也造成了系统资源的浪费
- 大量硬编码,缺乏灵活性,不利于后期维护
- 参数的赋值和数据的封装全是手动进行
可能有些程序员还可以再列出一些 JDBC 的缺点,如果你已经很久没有使用过 JDBC 了,印象已经不深刻了,那么相信下面的代码能勾引起你的些许回忆。
public class Demo {
public List findAll() {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
List bookList = null;
try {
//加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//通过驱动管理类获取数据库链接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123");
//定义 sql 语句 ?表示占位符
String sql = "select * from t_book where author = ?";
//获取预处理 statement
preparedStatement = connection.prepareStatement(sql);
//设置参数,第一个参数为 sql 语句中参数的序号(从 1 开始),第二个参数为设置的参数值
preparedStatement.setString(1, "张三");
//向数据库发出 sql 执行查询,查询出结果集
resultSet = preparedStatement.executeQuery();
//遍历查询结果集
bookList = new ArrayList<>();
while (resultSet.next()) {
Book book = new Book();
book.setId(resultSet.getInt("id"));
book.setName(resultSet.getString("bname"));
book.setAuthor(resultSet.getString("author"));
book.setPrice(resultSet.getDouble("price"));
bookList.add(book);
}
return bookList;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
//释放资源
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
正是因为 JDBC 存在着各种问题,所以才导致很多持久层框架应运而生,例如:Hibernate和 MyBatis,这两个都是目前比较流行的持久层框架,都对 JDBC 进行了更高级的封装和优化
很多程序员其实都亲自尝试过自己对 JDBC 进行封装和优化,设计并编写过一些 API,每个程序员在做这个事情时,可能设计以及实现的思想都是不一样的,这些思想各有特点,各有千秋,可以分为两大类:
第一类:着重对 JDBC 进行 API 层的抽取和封装,以及功能的增强,典型代表是 Apache 的DbUtils
使用 DbUtils 时仍然需要编写 sql 语句并手动进行数据封装,但是 API 的使用比JDBC方便了很多,下面是使用 DbUtils 的代码片段
@Test
public void testQuery() {
//创建 queryRunner 对象,用来操作 sql 语句
QueryRunner qr = new QueryRunner(JDBCUtils.getDataSource());
//编写 sql 语句
String sql = "select * from user";
//执行 sql 语句
try {
List list = qr.query(sql, new BeanListHandler(User.class));
System.out.println(list);
} catch (SQLException e) {
e.printStackTrace();
}
}
第二类:借鉴面向对象的思想,让程序员以操作对象的方式操作数据库,无需编写 sql 语句,典型代表是 ORM。ORM(Object Relational Mapping)吸收了面向对象的思想,把对 sql 的操作转换为对象的操作,从而让程序员使用起来更加方便和易于接受。这种转换是通过对象和表之间的元数据映射实现的,这是实现 ORM 的关键,如下图所示
由于类和表之间以及属性和字段之间建立起了映射关系,所以,通过 sql 对表的操作就可以转换为对象的操作,程序员从此无需编写 sql 语句,由框架根据映射关系自动生成,这就是 ORM 思想。
目前比较流行的 Hibernate 和 MyBatis 都采用了 ORM 思想,一般我们把 Hibernate 称之为全自动的 ORM 框架,把 MyBatis 称之为半自动的 ORM 框架。使用过这两个框架的程序员,对于 ORM 一定不会陌生。同时,ORM 也是 JPA(SUN 推出的持久层规范)的核心内容
二.ORM 的经典应用:MyBatis
MyBatis框架也应用了ORM思想,一般我们把它称之为半自动的ORM框架,跟Hibernate相比,MyBatis 更加轻量,更加灵活,为了保证这一点,程序员在使用 MyBatis 时需要自己编写 sql 语句,但是 API 的使用依然像 Hibernate 一样简单方便
2.1 MyBatis 案例
1.创建Maven项目 添加依赖
mysql
mysql-connector-java
5.1.47
org.mybatis
mybatis
3.4.5
com.alibaba
druid
1.1.12
2. MyBatis 核心配置文件 SqlMapConfig.xml
3.数据库表和对应实体类
CREATE TABLE `user_info` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id 无意义',
`username` varchar(16) DEFAULT NULL COMMENT '用户名',
`password` varchar(20) DEFAULT NULL COMMENT '密码',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
public class UserInfo {
private Integer id;
private String username;
private String password;
//省略getter setter 方法
}
4. Mapper 接口 UserInfoMapper.java
public interface UserInfoMapper {
/**
* 查询所有数据
* @return 如果找到了,返回泛型为UserInfo的List集合,如果没有找到,就返回null
*/
List findAll();
}
MyBatis 其实会采用默认规则自动建立表和实体类之间,属性和字段之间的映射关系
5. UserInfo对应的xml配置文件
6. 测试
package cn.icanci.test;
import cn.icanci.dao.IUserDao;
import cn.icanci.domain.UserInfo;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
import java.util.List;
/**
* @Author: icanci
* @ProjectName: mybatis
* @PackageName: cn.icanci.test
* @Date: Created in 2020/2/20 13:26
* @ClassAction: 测试
*/
public class Test {
public static void main(String[] args) throws Exception {
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory build = builder.build(inputStream);
SqlSession session = build.openSession();
IUserDao mapper = session.getMapper(IUserDao.class);
List all = mapper.findAll();
all.forEach(System.out::println);
session.close();
}
}
打印
UserInfo{id=1, username='haxi', password='456456'}
UserInfo{id=2, username='阿斯顿', password='2134fds'}
UserInfo{id=3, username='kkq', password='asdasd'}
2.2 MyBatis 的 的 ORM 实现原理
通过上述案例来讲解一下 MyBatis 框架是如何应用 ORM 思想的,一起剖析一下 MyBatis 3.x 的内部实现原理
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory build = builder.build(inputStream);
这三行代码先是创建了 SqlSessionFactoryBuilder 对象,然后获得了 MyBatis 核心配置文件的输入流,最后调用了 build 方法,我们接下来跟踪一下该方法的源码
/**
* Copyright 2009-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.session;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Properties;
import org.apache.ibatis.builder.xml.XMLConfigBuilder;
import org.apache.ibatis.exceptions.ExceptionFactory;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;
/**
* Builds {@link SqlSession} instances.
*
* @author Clinton Begin
*/
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}
public SqlSessionFactory build(Reader reader, String environment) {
return build(reader, environment, null);
}
public SqlSessionFactory build(Reader reader, Properties properties) {
return build(reader, null, properties);
}
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment) {
return build(inputStream, environment, null);
}
public SqlSessionFactory build(InputStream inputStream, Properties properties) {
return build(inputStream, null, properties);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
在上述源码中
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
创建了一个 xml 解析器对象,并在下面
return build(parser.parse());
对 MyBatis 核心配置文件进行了解析,拿到了数据库连接数据以及映射配置文件中的数据(包括我们编写的 sql 语句和自定义的resultMap)
在Mybatis中,通过读取 Configuration 对象中的数据分别创建了 Environment对象,事务对象,Executor 对象等,并最终直接 new 了一个 DefaultSalSession 对象(SqlSession 接口的实现类),该对象是 MyBatis API 的核心对象
通过代理工厂最终把我们自定义的 Mapper 接口的代理对象创建了出来
当调用代理对象的 getEmpTotalByDept()方法时,框架内部会调用 MapperProxy 的 invoke 方法
public T getMapper(Class type, SqlSession sqlSession) {
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
}
最后 通过调用 queryFromDatabase 方法最终执行了 sql 语句并将查询结果按照自定义的 resultMap 进行了封装
三.自定义一个简单的 ORM 框架
自己定义一个简单的 ORM 框架(名为 MiniORM),希望能通过这种方式亲自零距离的去应用一下 ORM
3.1 MiniORM 框架的结构设计
1. 第一层为配置层:
miniORM.cfg.xml 是框架的核心配置文件,主要用来设置数据库连接信息和映射配置文件路径信息
Xxx.mapper.xml 是框架的映射配置文件,主要用来设置类和表之间以及属性和字段之间的映射关系
Xxx.java 是带有映射注解的实体类,主要用来设置类和表之间以及属性和字段之间的映射关系,和 Xxx.mapper.xml 的作用一样,只不过采用的是注解方式,两者二选一
2. 第二层为解析层:
Dom4jUtil 类用来解析 miniORM.cfg.xml 和 Xxx.mapper.xml 两个配置文件的数据
AnnotationUtil 类用来解析实体类中的映射注解
3. 第三层为封装层:
ORMConfig 类用来封装和存储从 miniORM.cfg.xml 文件中解析得到的数据
Mapper 类用来封装和存储从 Xxx.mapper.xml 或实体类中解析得到的映射数据
4. 第四层为功能层:
ORMSession 类主要用来从 ORMConfig 和 Mapper 中获取相关数据,然后生成 sql 语句,
最后通过对 JDBC 的封装最终实现增删改查功能
3.2 MiniORM 框架的代码实现
1.pom.xml配置文件
4.0.0
cn.icanci
miniorm
1.0-SNAPSHOT
jar
org.dom4j
dom4j
2.1.1
org.apache.maven.plugins
maven-compiler-plugin
3.2
1.8
UTF-8
2. 自定义三个注解
package cn.icanci.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Author: icanci
* @ProjectName: miniorm
* @PackageName: cn.icanci.annotation
* @Date: Created in 2020/2/20 19:20
* @ClassAction: 注解 用来设置表明
*/
//运行期间保留注解的信息
@Retention(RetentionPolicy.RUNTIME)
//设置注解用到什么地方
@Target({ElementType.TYPE})
public @interface ORMTable {
public String name() default "";
}
package cn.icanci.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Author: icanci
* @ProjectName: miniorm
* @PackageName: cn.icanci.annotation
* @Date: Created in 2020/2/20 19:22
* @ClassAction: 用来设置id
*/
//运行期间保留注解的信息
@Retention(RetentionPolicy.RUNTIME)
//设置注解用到什么地方
@Target({ElementType.FIELD})
public @interface ORMId {
}
package cn.icanci.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Author: icanci
* @ProjectName: miniorm
* @PackageName: cn.icanci.annotation
* @Date: Created in 2020/2/20 19:23
* @ClassAction: 注解 用来设置属性是哪个字段
*/
//运行期间保留注解的信息
@Retention(RetentionPolicy.RUNTIME)
//设置注解用到什么地方
@Target({ElementType.FIELD})
public @interface ORMColumn {
public String name() default "";
}
3. 解析数据工具类
Dom4jUtil
package cn.icanci.utils;
import java.io.File;
import java.util.*;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
/**
* 基于dom4j的工具类
*/
public class Dom4jUtil {
/**
* 通过文件的路径获取xml的document对象
*
* @param path 文件的路径
* @return 返回文档对象
*/
public static Document getXMLByFilePath(String path) {
if (null == path) {
return null;
}
Document document = null;
try {
SAXReader reader = new SAXReader();
document = reader.read(new File(path));
} catch (Exception e) {
e.printStackTrace();
}
return document;
}
/**
* 获得某文档中某元素内某属性的值和元素的文本信息
*
* @param document xml文档对象
* @param elementName 元素名
* @param attrName 属性名
* @return 返回一个Map集合
*/
public static Map Elements2Map(Document document, String elementName, String attrName) {
List propList = document.getRootElement().elements(elementName);
Map propConfig = new HashMap<>();
for (Element element : propList) {
String key = element.attribute(attrName).getValue();
String value = element.getTextTrim();
propConfig.put(key, value);
}
return propConfig;
}
/**
* 针对mapper.xml文件,获得映射信息并存到Map集合中
*
* @param document xml文档对象
* @return 返回一个Map集合
*/
public static Map Elements2Map(Document document) {
Element classElement = document.getRootElement().element("class");
Map mapping = new HashMap<>();
Element idElement = classElement.element("id");
String idKey = idElement.attribute("name").getValue();
String idValue = idElement.attribute("column").getValue();
mapping.put(idKey, idValue);
List propElements = classElement.elements("property");
for (Element element : propElements) {
String propKey = element.attribute("name").getValue();
String propValue = element.attribute("column").getValue();
mapping.put(propKey, propValue);
}
return mapping;
}
/**
* 针对mapper.xml文件,获得主键的映射信息并存到Map集合中
*
* @param document xml文档对象
* @return 返回一个Map集合
*/
public static Map ElementsID2Map(Document document) {
Element classElement = document.getRootElement().element("class");
Map mapping = new HashMap<>();
Element idElement = classElement.element("id");
String idKey = idElement.attribute("name").getValue();
String idValue = idElement.attribute("column").getValue();
mapping.put(idKey, idValue);
return mapping;
}
/**
* 获得某文档中某元素内某属性的值
*
* @param document xml文档对象
* @param elementName 元素名
* @param attrName 属性名
* @return 返回一个Set集合
*/
public static Set Elements2Set(Document document, String elementName, String attrName) {
List mappingList = document.getRootElement().elements(elementName);
Set mappingSet = new HashSet<>();
for (Element element : mappingList) {
String value = element.attribute(attrName).getValue();
mappingSet.add(value);
}
return mappingSet;
}
/**
* 获得某文档中某元素内某属性的值
*
* @param document xml文档对象
* @param elementName 元素名
* @param attrName 属性名
* @return 返回一个Set集合
*/
public static String getPropValue(Document document, String elementName, String attrName) {
Element element = (Element) document.getRootElement().elements(elementName).get(0);
return element.attribute(attrName).getValue();
}
}
AnnotationUtil
package cn.icanci.utils;
import cn.icanci.annotation.ORMColumn;
import cn.icanci.annotation.ORMId;
import cn.icanci.annotation.ORMTable;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* 使用反射解析实体类中注解的工具类
*/
public class AnnotationUtil {
/**
* 得到的类名
*/
public static String getClassName(Class clz) {
return clz.getName();
}
/**
* 得到ORMTable注解中的表名
*/
public static String getTableName(Class clz) {
if (clz.isAnnotationPresent(ORMTable.class)) {
ORMTable ormTable = (ORMTable) clz.getAnnotation(ORMTable.class);
return ormTable.name();
} else {
System.out.println("缺少ORMTable注解");
return null;
}
}
/**
* 得到主键属性和对应的字段
*/
public static Map getIdMapper(Class clz) {
boolean flag = true;
Map map = new HashMap<>();
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(ORMId.class)) {
flag = false;
String fieldName = field.getName();
if (field.isAnnotationPresent(ORMColumn.class)) {
ORMColumn ormColumn = field.getAnnotation(ORMColumn.class);
String columnName = ormColumn.name();
map.put(fieldName, columnName);
break;
} else {
System.out.println("缺少ORMColumn注解");
}
}
}
if (flag) {
System.out.println("缺少ORMId注解");
}
return map;
}
/**
* 得到类中所有属性和对应的字段
*/
public static Map getPropMapping(Class clz) {
Map map = new HashMap<>();
map.putAll(getIdMapper(clz));
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(ORMColumn.class)) {
ORMColumn ormColumn = field.getAnnotation(ORMColumn.class);
String fieldName = field.getName();
String columnName = ormColumn.name();
map.put(fieldName, columnName);
}
}
return map;
}
/**
* 获得某包下面的所有类名
*/
public static Set getClassNameByPackage(String packagePath) {
Set names = new HashSet<>();
String packageFile = packagePath.replace(".", "/");
String classpath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
if (classpath == null) {
classpath = Thread.currentThread().getContextClassLoader().getResource("/").getPath();
}
try {
classpath = java.net.URLDecoder.decode(classpath, "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
File dir = new File(classpath + packageFile);
if (dir.exists()) {
File[] files = dir.listFiles();
for (File f : files) {
String name = f.getName();
if (f.isFile() && name.endsWith(".class")) {
name = packagePath + "." + name.substring(0, name.lastIndexOf("."));
names.add(name);
}
}
} else {
System.out.println("包路径不存在");
}
return names;
}
}
4. miniORM.cfg.xml 是框架的核心配置文件,主要用来设置数据库连接信息和映射配置文件路径信息,源码如下所示:
jdbc:mysql://localhost:3306/test
com.mysql.jdbc.Driver
root
ok
5.Mapper用来封装和存储映射信息
package cn.icanci.core;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: icanci
* @ProjectName: miniorm
* @PackageName: cn.icanci.core
* @Date: Created in 2020/2/20 19:39
* @ClassAction: 用来封装和存储映射信息
*/
public class Mapper {
/**
* 类名
*/
private String className;
/**
* 表名
*/
private String tableName;
/**
* 主键信息
*/
private Map idMapper = new HashMap<>();
/**
* 普通属性和字段信息
*/
private Map propMapper = new HashMap<>();
@Override
public String toString() {
return "Mapper{" +
"className='" + className + '\'' +
", tableName='" + tableName + '\'' +
", idMapper=" + idMapper +
", propMapper=" + propMapper +
'}';
}
}
6.ORMConfig 用来解析和封装框架的配置文件
package cn.icanci.core;
import cn.icanci.utils.AnnotationUtil;
import cn.icanci.utils.Dom4jUtil;
import org.dom4j.Document;
import java.io.File;
import java.net.URLDecoder;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @Author: icanci
* @ProjectName: miniorm
* @PackageName: cn.icanci.core
* @Date: Created in 2020/2/20 19:45
* @ClassAction: 用来解析和封装框架的配置文件
*/
public class ORMConfig {
/**
* classpath 路径
*/
private static String classpath;
/**
* 核心配置文件
*/
private static File cfgFile;
/**
* 标签中的数据
*/
private static Map propConfig;
/**
* 映射文件路径
*/
private static Set mappingSet;
/**
* 实体类
*/
private static Set entitySet;
/**
* 映射信息
*/
public static List mapperList;
static {
//得到的classpath路径
classpath=Thread.currentThread().getContextClassLoader().getResource("").getPath();
//针对中文路径进行转码
try {
classpath = URLDecoder.decode(classpath, "utf-8");
}catch (Exception e){
e.printStackTrace();
}
//得到核心配置文件
System.out.println(classpath);
cfgFile=new File(classpath + "miniORM.cfg.xml");
if(cfgFile.exists()){
// 解析核心配置文件中的数据
Document document=Dom4jUtil.getXMLByFilePath(cfgFile.getPath());
propConfig=Dom4jUtil.Elements2Map(document,"property","name");
mappingSet=Dom4jUtil.Elements2Set(document,"mapping","resource");
entitySet=Dom4jUtil.Elements2Set(document,"entity","package");
}
else {
cfgFile = null;
System.out.println("未找到核心配置文件miniORM.cfg.xml");
}
}
//从propConfig集合中获取数据并连接数据库
private Connection getConnection() throws Exception{
String url=propConfig.get("connection.url");
String driverClass=propConfig.get("connection.driverClass");
String username=propConfig.get("connection.username");
String password=propConfig.get("connection.password");
Class.forName(driverClass);
Connection connection=DriverManager.getConnection(url,username,password);
connection.setAutoCommit(true);
return connection;
}
private void getMapping () throws Exception{
mapperList = new ArrayList<>();
//1. 解析xxx.mapper.xml文件拿到映射数据
for (String xmlPath:mappingSet){
Document document=Dom4jUtil.getXMLByFilePath(classpath+xmlPath);
String className=Dom4jUtil.getPropValue(document,"class","name");
String tableName=Dom4jUtil.getPropValue(document,"class","table");
Map id_id=Dom4jUtil.ElementsID2Map(document);
Map mapping = Dom4jUtil.Elements2Map(document);
Mapper mapper=new Mapper();
mapper.setTableName(tableName);
mapper.setClassName(className);
mapper.setIdMapper(id_id);
mapper.setPropMapper(mapping);
mapperList.add(mapper);
}
//2. 解析实体类中的注解拿到映射数据
for(String packagePath:entitySet){
Set nameSet=AnnotationUtil.getClassNameByPackage(packagePath);
for(String name:nameSet){
Class clz=Class.forName(name);
String className=AnnotationUtil.getClassName(clz);
String tableName=AnnotationUtil.getTableName(clz);
Map id_id=AnnotationUtil.getIdMapper(clz);
Map mapping=AnnotationUtil.getPropMapping(clz);
Mapper mapper=new Mapper();
mapper.setTableName(tableName);
mapper.setClassName(className);
mapper.setIdMapper(id_id);
mapper.setPropMapper(mapping);
mapperList.add(mapper);
}
}
}
//创建ORMSession对象
public ORMSession buildORMSession() throws Exception{
//1. 连接数据库
Connection connection=getConnection();
//2. 得到映射数据
getMapping();
//3. 创建ORMSession对象
return new ORMSession(connection);
}
}
7. ORMSession 实现增删查
package cn.icanci.core;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @Author: icanci
* @ProjectName: miniorm
* @PackageName: cn.icanci.core
* @Date: Created in 2020/2/20 21:33
* @ClassAction:
*/
//该类生成sql并实现增删改查功能
public class ORMSession {
private Connection connection;
public ORMSession(Connection conn) {
this.connection = conn;
}
//保存数据
public void save(Object entity) throws Exception {
String insertSQL = "";
//1. 从ORMConfig中获得保存有映射信息的集合
List mapperList = ORMConfig.mapperList;
//2. 遍历集合,从集合中找到和entity参数相对应的mapper对象
for (Mapper mapper : mapperList) {
if (mapper.getClassName().equals(entity.getClass().getName())) {
String tableName = mapper.getTableName();
String insertSQL1 = "insert into " + tableName + "( ";
String insertSQL2 = " ) values ( ";
//3. 得到当前对象所属类中的所有属性
Field[] fields = entity.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
//4. 遍历过程中根据属性得到字段名
String columnName = mapper.getPropMapper().get(field.getName());
//5. 遍历过程中根据属性得到它的值
String columnValue = field.get(entity).toString();
//6. 拼接sql语句
insertSQL1 += columnName + ",";
insertSQL2 += "'" + columnValue + "',";
}
insertSQL = insertSQL1.substring(0, insertSQL1.length() - 1) + insertSQL2.substring(0, insertSQL2.length() - 1) + " )";
break;
}
}
// 把sql语句打印到控制台
System.out.println("MiniORM-save: " + insertSQL);
//7. 通过JDBC发送并执行sql
PreparedStatement statement = connection.prepareStatement(insertSQL);
statement.executeUpdate();
statement.close();
}
//根据主键进行数据删除 delete from 表名 where 主键 = 值
public void delete(Object entity) throws Exception {
String delSQL = "delete from ";
//1. 从ORMConfig中获得保存有映射信息的集合
List mapperList = ORMConfig.mapperList;
//2. 遍历集合,从集合中找到和entity参数相对应的mapper对象
for (Mapper mapper : mapperList) {
if (mapper.getClassName().equals(entity.getClass().getName())) {
// 3. 得到我们想要的mapper对象,并得到表名
String tableName = mapper.getTableName();
delSQL += tableName + " where ";
// 4. 得到主键的字段名和属性名
Object[] idProp = mapper.getIdMapper().keySet().toArray(); //idProp[0]
Object[] idColumn = mapper.getIdMapper().values().toArray(); //idColumn[0]
// 5. 得到主键的值
Field field = entity.getClass().getDeclaredField(idProp[0].toString());
field.setAccessible(true);
String idVal = field.get(entity).toString();
// 6. 拼接sql
delSQL += idColumn[0].toString() + " = " + idVal;
// 把sql语句打印到控制台
System.out.println("MiniORM-delete: " + delSQL);
break;
}
}
//7. 通过JDBC发送并执行sql
PreparedStatement statement = connection.prepareStatement(delSQL);
statement.executeUpdate();
statement.close();
}
// 根据主键进行查询 select * from 表名 where 主键字段 = 值
public Object findOne(Class clz, Object id) throws Exception{
String querySQL = "select * from ";
//1. 从ORMConfig中得到存有映射信息的集合
List mapperList=ORMConfig.mapperList;
//2. 遍历集合拿到我们想要的mapper对象
for (Mapper mapper : mapperList) {
if (mapper.getClassName().equals(clz.getName())) {
// 3. 获得表名
String tableName = mapper.getTableName();
//4. 获得主键字段名
Object[] idColumn = mapper.getIdMapper().values().toArray(); //idColumn[0]
//5. 拼接sql
querySQL += tableName + " where " + idColumn[0].toString() + " = " + id;
break;
}
}
System.out.println("MiniORM-findOne:" +querySQL);
//6. 通过jdbc发送并执行sql, 得到结果集
PreparedStatement statement=connection.prepareStatement(querySQL);
ResultSet rs=statement.executeQuery();
//7. 封装结果集,返回对象
if(rs.next()){
// 查询到一行数据
// 8.创建一个对象,目前属性的值都是初始值
Object obj=clz.newInstance();
// 9. 遍历mapperList集合找到我们想要的mapper对象
for(Mapper mapper:mapperList){
if (mapper.getClassName().equals(clz.getName())) {
//10. 得到存有属性-字段的映射信息
Map propMap = mapper.getPropMapper();
//11. 遍历集合分别拿到属性名和字段名
Set keySet = propMap.keySet();
for(String prop:keySet){ //prop就是属性名
String column = propMap.get(prop); //column就是和属性对应的字段名
Field field = clz.getDeclaredField(prop);
field.setAccessible(true);
field.set(obj,rs.getObject(column));
}
break;
}
}
//12. 释放资源
statement.close();
rs.close();
//13. 返回查询出来的对象
return obj;
}else {
// 没有查到数据
return null;
}
}
//关闭连接,释放资源
public void close() throws Exception{
if(connection!=null){
connection.close();
connection = null;
}
}
}
执行测试
1.测试保存
@org.junit.Test
public void test1() throws Exception {
ormConfig = new cn.icanci.core.ORMConfig();
System.out.println(ormConfig);
UserInfo userInfo = new UserInfo();
userInfo.setUsername("哈希哈系");
userInfo.setPassword("demodemo");
System.out.println(userInfo);
cn.icanci.core.ORMSession ormSession = ormConfig.buildORMSession();
ormSession.save(userInfo);
ormSession.close();
}
2.测试查询
@org.junit.Test
public void test2() throws Exception {
ormConfig = new cn.icanci.core.ORMConfig();
System.out.println(ormConfig);
cn.icanci.core.ORMSession ormSession = ormConfig.buildORMSession();
UserInfo one = (UserInfo) ormSession.findOne(UserInfo.class, 2);
System.out.println(one);
ormSession.close();
}
3.测试删除
@org.junit.Test
public void test3() throws Exception {
ormConfig = new cn.icanci.core.ORMConfig();
System.out.println(ormConfig);
cn.icanci.core.ORMSession ormSession = ormConfig.buildORMSession();
UserInfo one = (UserInfo) ormSession.findOne(UserInfo.class, 2);
ormSession.delete(one);
ormSession.close();
}