简易ORM框架
# 一、需求背景
业务逻辑的开发最终会落实到数据的操作,数据操作本质就是CRUD
,CRUD
的上层封装就是ORM框架
。
我们接触过许多的ORM框架
:JDBC、MyBatis、Hibernate等,ORM对象关系映射,是一种程序设计技术,用于实现面对不同的模块、Bean等,我们能够快速进行数据操作。可以说 ORM框架
就是连接数据库的桥梁,是一个数据持久层框架。
# 二、方案设计
ORM框架
主体功能包括:
- 参数映射
- SQL解析
- SQL执行
- 结果映射
我们设计一个简易框架,对外要提供CRUD
操作的接口,对内需要进行XML映射、数据库连接、SQL解析、SQL执行以及最后结果映射。
# 三、实现方案
# 1、XML实体映射
由于我们的SQL语句是在XML中,XML会包括一些如id
、namespce
、resultType
、parameterType
,我们需要一个实体来映射XML文件:
public class XNode {
/**
* 命名空间
*/
private String namespace;
/**
* 唯一id
*/
private String id;
/**
* 参数类型
*/
private String parameterType;
/**
* 返回类型
*/
private String resultType;
/**
* 执行的sql语句
*/
private String sql;
private Map<Integer, String> parameter;
public String getNamespace() {
return namespace;
}
public void setNamespace(String namespace) {
this.namespace = namespace;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getParameterType() {
return parameterType;
}
public void setParameterType(String parameterType) {
this.parameterType = parameterType;
}
public String getResultType() {
return resultType;
}
public void setResultType(String resultType) {
this.resultType = resultType;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
public Map<Integer, String> getParameter() {
return parameter;
}
public void setParameter(Map<Integer, String> parameter) {
this.parameter = parameter;
}
}
# 2、资源加载
数据源是在配置文件中进行配置的,我们需要书写一个资源加载类,对数据库进行加载:
public class Resources {
public static Reader getResourceAsReader(String resource) throws IOException {
return new InputStreamReader(getResourceAsStream(resource));
}
private static InputStream getResourceAsStream(String resource) throws IOException {
ClassLoader[] classLoaders = getClassLoaders();
for (ClassLoader classLoader : classLoaders) {
InputStream inputStream = classLoader.getResourceAsStream(resource);
if (null != inputStream) {
return inputStream;
}
}
throw new IOException("Could not find resource " + resource);
}
private static ClassLoader[] getClassLoaders() {
return new ClassLoader[]{
ClassLoader.getSystemClassLoader(),
Thread.currentThread().getContextClassLoader()};
}
}
# 3、配置类
配置类主要是对数据库连接
、数据源
、mapper
进行管理:
public class Configuration {
protected Connection connection;
protected Map<String, String> dataSource;
protected Map<String, XNode> mapperElement;
public void setConnection(Connection connection) {
this.connection = connection;
}
public void setDataSource(Map<String, String> dataSource) {
this.dataSource = dataSource;
}
public void setMapperElement(Map<String, XNode> mapperElement) {
this.mapperElement = mapperElement;
}
}
# 4、连接创建工厂
当我们有数据库操作的时候,都会去获取一次SqlSession
,我们需要一个工厂来建立这个连接,并且为这个连接配置相应的属性:
public interface SqlSessionFactory {
SqlSession openSession();
}
public class BaseSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public BaseSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return new BaseSqlSession(configuration.connection, configuration.mapperElement);
}
}
当工厂创建连接的时候,会传递Configuration
,其中会去配置数据库连接
、数据源
、mapper
# 5、数据操作
连接建立完毕之后,我们就可以在这个连接中对数据进行操作:
public interface SqlSession {
/**
* 带条件的查询
*/
<T> T selectOne(String statement, Object parameter);
/**
* 查询全部数据
*/
<T> List<T> selectList(String statement);
/**
* 带条件查询全部数据
*/
<T> List<T> selectList(String statement, Object parameter);
void close();
}
public class BaseSqlSession implements SqlSession {
/**
* sql的连接
*/
private Connection connection;
/**
* mapper
*/
private Map<String, XNode> mapperElement;
public BaseSqlSession(Connection connection, Map<String, XNode> mapperElement) {
this.connection = connection;
this.mapperElement = mapperElement;
}
@Override
public <T> T selectOne(String statement, Object parameter) {
try {
XNode xNode = mapperElement.get(statement);
Map<Integer, String> parameterMap = xNode.getParameter();
PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
buildParameter(preparedStatement, parameter, parameterMap);
ResultSet resultSet = preparedStatement.executeQuery();
List<T> objects = resultSetToObj(resultSet, Class.forName(xNode.getResultType()));
return objects.get(0);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public <T> List<T> selectList(String statement) {
try {
XNode xNode = mapperElement.get(statement);
PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
ResultSet resultSet = preparedStatement.executeQuery();
List<T> objects = resultSetToObj(resultSet, Class.forName(xNode.getResultType()));
return objects;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public <T> List<T> selectList(String statement, Object parameter) {
XNode xNode = mapperElement.get(statement);
Map<Integer, String> parameterMap = xNode.getParameter();
try {
PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
buildParameter(preparedStatement, parameter, parameterMap);
ResultSet resultSet = preparedStatement.executeQuery();
return resultSetToObj(resultSet, Class.forName(xNode.getResultType()));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public void close() {
if (null == connection) {
return;
}
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* resultSet转换为Object
*/
private <T> List<T> resultSetToObj(ResultSet resultSet, Class<?> clazz) {
List<T> list = new ArrayList<>();
try {
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
// 每次遍历行值
while (resultSet.next()) {
T obj = (T) clazz.newInstance();
for (int i = 1; i <= columnCount; i++) {
Object value = resultSet.getObject(i);
String columnName = metaData.getColumnName(i);
String setMethod = "set" + columnName.substring(0, 1).toUpperCase() + columnName.substring(1);
Method method;
if (value instanceof Timestamp) {
method = clazz.getMethod(setMethod, Date.class);
} else {
method = clazz.getMethod(setMethod, value.getClass());
}
method.invoke(obj, value);
}
list.add(obj);
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
/**
* 构建查询参数
*/
private void buildParameter(PreparedStatement preparedStatement, Object parameter, Map<Integer, String> parameterMap) throws SQLException, IllegalAccessException {
int size = parameterMap.size();
// 单个参数
if (parameter instanceof Long) {
for (int i = 1; i <= size; i++) {
preparedStatement.setLong(i, Long.parseLong(parameter.toString()));
}
return;
}
if (parameter instanceof Integer) {
for (int i = 1; i <= size; i++) {
preparedStatement.setInt(i, Integer.parseInt(parameter.toString()));
}
return;
}
if (parameter instanceof String) {
for (int i = 1; i <= size; i++) {
preparedStatement.setString(i, parameter.toString());
}
return;
}
Map<String, Object> fieldMap = new HashMap<>();
// 对象参数
Field[] declaredFields = parameter.getClass().getDeclaredFields();
for (Field field : declaredFields) {
String name = field.getName();
field.setAccessible(true);
Object obj = field.get(parameter);
field.setAccessible(false);
fieldMap.put(name, obj);
}
for (int i = 1; i <= size; i++) {
String parameterDefine = parameterMap.get(i);
Object obj = fieldMap.get(parameterDefine);
if (obj instanceof Short) {
preparedStatement.setShort(i, Short.parseShort(obj.toString()));
continue;
}
if (obj instanceof Integer) {
preparedStatement.setInt(i, Integer.parseInt(obj.toString()));
continue;
}
if (obj instanceof Long) {
preparedStatement.setLong(i, Long.parseLong(obj.toString()));
continue;
}
if (obj instanceof String) {
preparedStatement.setString(i, obj.toString());
continue;
}
if (obj instanceof Date) {
preparedStatement.setDate(i, (java.sql.Date) obj);
}
}
}
}
resultSetToObj
:将sql执行后的结果转换为Bean对象buildParamete
:构建查询的参数
# 6、工厂连接构造器
public class SqlSessionFactoryBuilder {
public BaseSqlSessionFactory build(Reader reader) {
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(new InputSource(reader));
Configuration configuration = parseConfiguration(document.getRootElement());
return new BaseSqlSessionFactory(configuration);
} catch (DocumentException e) {
e.printStackTrace();
}
return null;
}
// 解析配置
private Configuration parseConfiguration(Element root) {
Configuration configuration = new Configuration();
configuration.setDataSource(dataSource(root.selectNodes("//dataSource")));
configuration.setConnection(connection(configuration.dataSource));
configuration.setMapperElement(mapperElement(root.selectNodes("mappers")));
return configuration;
}
// 获取数据源配置信息
private Map<String, String> dataSource(List<Element> list) {
Map<String, String> dataSource = new HashMap<>(4);
Element element = list.get(0);
List content = element.content();
for (Object o : content) {
Element e = (Element) o;
String name = e.attributeValue("name");
String value = e.attributeValue("value");
dataSource.put(name, value);
}
return dataSource;
}
// 获取数据库连接
private Connection connection(Map<String, String> dataSource) {
try {
Class.forName(dataSource.get("driver"));
return DriverManager.getConnection(dataSource.get("url"), dataSource.get("username"), dataSource.get("password"));
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
return null;
}
// 获取SQL语句信息
private Map<String, XNode> mapperElement(List<Element> list) {
Map<String, XNode> map = new HashMap<>();
Element element = list.get(0);
List content = element.content();
for (Object o : content) {
Element e = (Element) o;
String resource = e.attributeValue("resource");
try {
Reader reader = Resources.getResourceAsReader(resource);
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(new InputSource(reader));
Element root = document.getRootElement();
//命名空间
String namespace = root.attributeValue("namespace");
// SELECT
List<Element> selectNodes = root.selectNodes("select");
for (Element node : selectNodes) {
String id = node.attributeValue("id");
String parameterType = node.attributeValue("parameterType");
String resultType = node.attributeValue("resultType");
String sql = node.getText();
// ? 匹配
Map<Integer, String> parameter = new HashMap<>();
Pattern pattern = Pattern.compile("(#\\{(.*?)})");
Matcher matcher = pattern.matcher(sql);
for (int i = 1; matcher.find(); i++) {
String g1 = matcher.group(1);
String g2 = matcher.group(2);
parameter.put(i, g2);
sql = sql.replace(g1, "?");
}
XNode xNode = new XNode();
xNode.setNamespace(namespace);
xNode.setId(id);
xNode.setParameterType(parameterType);
xNode.setResultType(resultType);
xNode.setSql(sql);
xNode.setParameter(parameter);
map.put(namespace + "." + id, xNode);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
return map;
}
}
build
:主要用于创建解析XML文件的类,以及初始化SqlSession工厂类BaseSqlSessionFactory
parseConfiguration
:主要对Configuration
需要的数据库连接
、数据源
、mapper
进行解析dataSource
:数据源配置connection
:数据库连接mapperElement
:解析sql语句
# 四、案例测试
# 1、配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test?useUnicode=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/Student_Mapper.xml"/>
</mappers>
</configuration>
# 2、mapper
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="me.xu.mybatis.mapper.StudentMapper">
<select id="queryStuInfoById" parameterType="java.lang.String" resultType="me.xu.mybatis.bean.Student">
SELECT id, name, age
FROM student
where id = #{id}
</select>
<select id="queryStuInfo" resultType="me.xu.mybatis.bean.Student">
SELECT id, name, age
FROM student
</select>
<select id="queryStuInfoByAge" parameterType="java.lang.Integer" resultType="me.xu.mybatis.bean.Student">
SELECT id, name, age
FROM student
where age = #{age}
</select>
</mapper>
数据操作接口
public interface StudentMapper {
Student queryStuInfoById(String id);
List<Student> queryStuInfo();
List<Student> queryStuInfoByAge();
}
实体
public class Student {
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
private String id;
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
# 3、数据操作
@SpringBootTest
class SpringMiniMybatisApplicationTests {
@Test
void queryStuInfoById() {
String resource = "mybatis-config-datasource.xml";
Reader reader;
try {
reader = Resources.getResourceAsReader(resource);
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = sqlMapper.openSession();
try {
Student student = session.selectOne("me.xu.mybatis.mapper.StudentMapper.queryStuInfoById", "stu12");
System.out.println(student);
} finally {
session.close();
reader.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
void queryStuInfo() {
String resource = "mybatis-config-datasource.xml";
Reader reader;
try {
reader = Resources.getResourceAsReader(resource);
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = sqlMapper.openSession();
try {
List<Student> stuList = session.selectList("me.xu.mybatis.mapper.StudentMapper.queryStuInfo");
stuList.forEach(System.out::println);
} finally {
session.close();
reader.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
void queryStuInfoByAge() {
String resource = "mybatis-config-datasource.xml";
Reader reader;
try {
reader = Resources.getResourceAsReader(resource);
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = sqlMapper.openSession();
try {
List<Student> stuList = session.selectList("me.xu.mybatis.mapper.StudentMapper.queryStuInfoByAge", 18);
stuList.forEach(System.out::println);
} finally {
session.close();
reader.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
# 参考
上次更新: 2024/06/29, 15:13:44