Mybatis学习(一):基础概念和简单自定义持久层框架demo
Mybatis学习(一):基础概念和简单自定义持久层框架demo
-
一、一个简单自定义持久层框架demo
-
1.1 普通的JDBC连接数据库
-
1.2 对jdbc存在的问题分析和解决
-
1.3 简单设计一个持久层框架
-
1.4 简单测试
一、一个简单自定义持久层框架demo
1.1 普通的JDBC连接数据库
public class Test {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//通过驱动管理类获取连接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis", "root", "root");
//定义SQL语句,其中?表示占位符
String sql = "select * from user where name = ?";
preparedStatement = connection.prepareStatement(sql);
//设置参数,参数是从1开始
preparedStatement.setString(1, "zhangsan");
//执行SQL查询数据库返回查询结果集
resultSet = preparedStatement.executeQuery();
//遍历查询结果集
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
User user = new User();
user.setId(id);
user.setName(name);
System.out.println(user);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException sqlException) {
sqlException.printStackTrace();
}
}
try {
if (preparedStatement != null) {
preparedStatement.close();
}
} catch (SQLException sqlException) {
sqlException.printStackTrace();
}
try {
if (connection != null) {
connection.close();
}
} catch (SQLException sqlException) {
sqlException.printStackTrace();
}
}
}
}
1.2 对jdbc存在的问题分析和解决
从上边1.1的手写JDBC连接可以看出:
- 频繁创建和释放数据库连接,性能损失;
- SQL语句是写死的,在实际项目中是会经常变化的;
- preparedStatement传入参数时也有硬编码的存在,不易维护;
- 从resultSet获取结果信息封装为Java某个对象时也存在硬编码问题;
我们可以用以下方法解决上述问题:
- 采用连接池的方式避免频繁创建、释放数据库连接;
- 对硬编码的问题我们可以用配置文件的方式去替代;
- 用反射及内省方法完成resultSet结果集到Java对象的封装;
1.3 简单设计一个持久层框架
用户端:引入自定义框架的jar包。
(1)提供一个Mapper.xml存储关于SQL的配置信息文件;
<mapper namespace="user">
<select id="selectUser" paramterType="com.testcus.pojo.User" resultType="com.testcus.pojo.User">
select * from user
</select>
</mapper>
namespace.id来组成statementId唯一标识SQL语句
(2)提供一个MapperConfig.xml存储关于数据源等配置信息文件,引入Mapper.xml;
<configuration>
<!-- 数据库配置信息 --> <property name="driverClass" value="com.mysql.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis"/> <property name="user" value="root"/> <property name="password" value="root"/>
<!-- 引入mapper信息 --> <mapper resource="mapper.xml"/>
</configuration>
框架端:
新建一个maven项目,引入相关依赖。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.customize</groupId>
<artifactId>customizeOrm</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
- 加载配置文件
配置文件以流的形式被读取出来,放在内存中不好操作,我们创建bean对象的方式来存储。
(1) 创建Resources类,根据配置文件的路径,加载配置文件成字节输入流存在内存中。
import java.io.InputStream;
public class Resources {
public static InputStream getResourcesAsStream(String path){
return Resources.class.getResourceAsStream(path);
}
}
(2) 创建Configuration对象存MapperConfig.xml解析到的内容;
package com.customer.config;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
public class Configuration {
/** * 数据源信息 */
private DataSource dataSource;
/** * mapper的相关信息 */
private Map<String,MappedStatement> map = new HashMap<>();
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public Map<String, MappedStatement> getMap() {
return map;
}
public void setMap(Map<String, MappedStatement> map) {
this.map = map;
}
}
(3) 创建MappedStatement对象存mapper.xml解析出来的sql信息、statement类型、输入参数类型、输出参数类型。
package com.customer.config;
public class MappedStatement {
/** * id */
private String id;
/** * SQL语句 */
private String sql;
/** * 输入参数 */
private Class<?> paramterType;
/** * 输出参数 */
private Class<?> resultType;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
public Class<?> getParamterType() {
return paramterType;
}
public void setParamterType(Class<?> paramterType) {
this.paramterType = paramterType;
}
public Class<?> getResultType() {
return resultType;
}
public void setResultType(Class<?> resultType) {
this.resultType = resultType;
}
}
- 解析配置文件(dom4j的方式)
(1)创建SqlSessionFactoryBuilder类:将解析出来的配置文件信息存在上边的容器对象中;创建SqlSessionFactory类,用来生产SqlSession对象(会话对象)
- 创建XMLConfigerBuilder类解析config配置文件,并将解析结果注入Configuration中:
package com.customer.io;
import com.customer.config.Configuration;
import com.customer.config.Resources;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import javax.naming.NamingException;
import java.beans.PropertyVetoException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;
public class XMLConfigerBuilder {
private Configuration configuration;
public XMLConfigerBuilder(Configuration configuration) {
this.configuration = configuration;
}
public Configuration parseConfiguration(InputStream inputStream) throws DocumentException, PropertyVetoException, NamingException, ClassNotFoundException {
Document document = new SAXReader().read(inputStream);
//取得<configuration>标签
Element rootElement = document.getRootElement();
Properties properties = new Properties();
List<Element> propertyNodes = rootElement.selectNodes("//property");
for (Element propertyNode : propertyNodes) {
String name = propertyNode.attributeValue("name");
String value = propertyNode.attributeValue("value");
properties.setProperty(name,value);
}
//连接池
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(properties.getProperty("driverClass"));
dataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
dataSource.setUser(properties.getProperty("user"));
dataSource.setPassword(properties.getProperty("password"));
//将连接池放入configuration对象中
configuration.setDataSource(dataSource);
//利用XMLMapperBuilder解析mapper
List<Element> mapperNodes = rootElement.selectNodes("//mapper");
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
for (Element mapperElement : mapperNodes) {
String path = mapperElement.attributeValue("resource");
xmlMapperBuilder.parseMapper(Resources.getResourcesAsStream(path));
}
return configuration;
}
}
- 创建XMLMapperBuilder类解析mapper.xml文件,将解析到的信息封装到MappedStatement对象并注入Configuration中;
在这里插入代码片
- 创建SqlSessionFactory接口类及默认实现DefaultSqlSessionFactory,生产SqlSession对象。
package com.customer.io;
import com.customer.config.Configuration;
import com.customer.config.MappedStatement;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.List;
public class XMLMapperBuilder {
private Configuration configuration;
public XMLMapperBuilder(Configuration configuration) {
this.configuration = configuration;
}
public Configuration parseMapper(InputStream inputStream) throws DocumentException, ClassNotFoundException {
Document document = new SAXReader().read(inputStream);
Element rootElement = document.getRootElement();
String namespace = rootElement.attributeValue("namespace");
List<Element> selectElements = rootElement.selectNodes("select");
for (Element selectElement : selectElements) {
String id = selectElement.attributeValue("id");
//入参类型
String paramterType = selectElement.attributeValue("paramterType");
//返回类型
String resultType = selectElement.attributeValue("resultType");
//sql语句
String sql = selectElement.getTextTrim();
//唯一标识 namespace+id
String key = namespace + id;
//将解析到的SQL信息封装到MappedStatement对象中
MappedStatement mappedStatement = new MappedStatement();
mappedStatement.setId(id);
mappedStatement.setSql(sql);
mappedStatement.setParamterType(getClassTypeByStr(paramterType));
mappedStatement.setResultType(getClassTypeByStr(resultType));
//将封装好的MappedStatement对象注入Configuration对象中
configuration.getMap().put(key,mappedStatement);
}
return configuration;
}
private Class<?> getClassTypeByStr(String typeStr) throws ClassNotFoundException {
return Class.forName(typeStr);
}
}
- 创建SqlSessionFactoryBuilder类生成SqlSessionFactory
package com.customer.sqlsession;
import com.customer.config.Configuration;
import com.customer.io.XMLConfigerBuilder;
import org.dom4j.DocumentException;
import javax.naming.NamingException;
import java.beans.PropertyVetoException;
import java.io.InputStream;
public class SqlSessionFactoryBuilder {
private Configuration configuration;
public SqlSessionFactoryBuilder() {
configuration = new Configuration();
}
public SqlSessionFactory build(InputStream inputStream) throws ClassNotFoundException, PropertyVetoException,
DocumentException, NamingException {
//1.解析配置文件得到Configuration对象
XMLConfigerBuilder xmlConfigerBuilder = new XMLConfigerBuilder(configuration);
xmlConfigerBuilder.parseConfiguration(inputStream);
//2.创建SqlSessionFactory
return new DefaultSqlSessionFactory(configuration);
}
}
- 创建SqlSessionFactory接口和其默认实现类DefaultSqlSessionFactory,主要负责生产SqlSession
package com.customer.sqlsession;
public interface SqlSessionFactory {
public SqlSession openSession();
}
package com.customer.sqlsession;
import com.customer.config.Configuration;
public class DefaultSqlSessionFactory implements SqlSessionFactory{
private Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return new DefaultSqlSession(configuration);
}
}
- 创建SqlSession接口及其默认实现类DefaultSqlSession,定义对数据库的操作:查询、新增、修改、删除
- 创建SqlSession接口,定义对数据库的操作:查询、新增、修改、删除
package com.customer.sqlsession;
import java.util.List;
public interface SqlSession {
<E> List<E> selectList(String statementId, Object... param);
<T> T selectOne(String statementId, Object... param);
}
- 创建SqlSession接口的默认实现类DefaultSqlSession
package com.customer.sqlsession;
import com.customer.config.Configuration;
import com.customer.config.MappedStatement;
import com.customer.executor.Executor;
import com.customer.executor.SimpleExecutor;
import java.util.List;
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
}
private Executor simpleExecutor = new SimpleExecutor();
@Override
public <E> List<E> selectList(String statementId, Object... param) {
MappedStatement mappedStatement = configuration.getMap().get(statementId);
return simpleExecutor.query(configuration, mappedStatement, param);
}
@Override
public <T> T selectOne(String statementId, Object... param) {
List<Object> objects = selectList(statementId, param);
if (objects.size() > 1) {
throw new RuntimeException("Too many returns !");
}
return (T) objects.get(0);
}
}
- 创建Executor接口及其实现类SimpleExecutor,定义query()方法执行jdbc。
- 创建Executor接口,定义query()方法
package com.customer.executor;
import com.customer.config.Configuration;
import com.customer.config.MappedStatement;
import java.util.List;
public interface Executor {
<E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object[] param);
}
- 创建Executor接口的实现类SimpleExecutor
package com.customer.executor;
import com.customer.config.Configuration;
import com.customer.config.MappedStatement;
import com.customer.util.GenericTokenParser;
import com.customer.util.ParameterMapping;
import com.customer.util.ParameterMappingTokenHandler;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class SimpleExecutor implements Executor {
private Connection connection = null;
@Override
public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object[] param) throws SQLException, NoSuchFieldException, IllegalAccessException, InstantiationException, IntrospectionException, InvocationTargetException {
//1.获取数据库连接
connection = configuration.getDataSource().getConnection();
//2.获取要执行的SQL语句
String sql = mappedStatement.getSql();
//3.对sql进行预编译处理
BoundSql boundSql = getBoundSql(sql);
//4.获取预编译对象PreparedStatement
PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getParseSql());
//5.设置参数对
Class<?> paramterType = mappedStatement.getParamterType();
List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
for (int i = 0; i < parameterMappingList.size(); i++) {
ParameterMapping parameterMapping = parameterMappingList.get(i);
String content = parameterMapping.getContent();
//利用反射
Field declaredField = paramterType.getDeclaredField(content);
declaredField.setAccessible(true);
Object o = declaredField.get(param[0]);
preparedStatement.setObject(i + 1, o);
}
//6.执行sql语句
ResultSet resultSet = preparedStatement.executeQuery();
//7.封装返回结果集
Class<?> resultType = mappedStatement.getResultType();
ArrayList<Object> list = new ArrayList();
while (resultSet.next()) {
Object o = resultType.newInstance();
ResultSetMetaData metaData = resultSet.getMetaData();
for (int i = 0; i < metaData.getColumnCount(); i++) {
//字段名
String columnName = metaData.getColumnName(i);
Object object = resultSet.getObject(columnName);
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultType);
Method writeMethod = propertyDescriptor.getWriteMethod();
writeMethod.invoke(o, object);
}
list.add(o);
}
return (List<E>) list;
}
private BoundSql getBoundSql(String sql) {
//标记处理类:配置标记解析器来完成对占位符的解析处理工作
ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
//解析得到的sql
String parse = genericTokenParser.parse(sql);
//#{}里面解析出来的参数名称
List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
return new BoundSql(parse, parameterMappings);
}
}
- 引入预编译处理工具类
package com.customer.util;
public class ParameterMapping {
private String content;
public ParameterMapping(String content) {
this.content = content;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
package com.customer.util;
/** * @author Clinton Begin */
public interface TokenHandler {
String handleToken(String content);
}
package com.customer.util;
import java.util.ArrayList;
import java.util.List;
public class ParameterMappingTokenHandler implements TokenHandler {
private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
// context是参数名称 #{id} #{username}
@Override
public String handleToken(String content) {
parameterMappings.add(buildParameterMapping(content));
return "?";
}
private ParameterMapping buildParameterMapping(String content) {
ParameterMapping parameterMapping = new ParameterMapping(content);
return parameterMapping;
}
public List<ParameterMapping> getParameterMappings() {
return parameterMappings;
}
public void setParameterMappings(List<ParameterMapping> parameterMappings) {
this.parameterMappings = parameterMappings;
}
}
/** * Copyright 2009-2017 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 com.customer.util;
/** * @author Clinton Begin */
public class GenericTokenParser {
private final String openToken; //开始标记
private final String closeToken; //结束标记
private final TokenHandler handler; //标记处理器
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}
/** * 解析${}和#{} * @param text * @return * 该方法主要实现了配置文件、脚本等片段中占位符的解析、处理工作,并返回最终需要的数据。 * 其中,解析工作由该方法完成,处理工作是由处理器handler的handleToken()方法来实现 */
public String parse(String text) {
// 验证参数问题,如果是null,就返回空字符串。
if (text == null || text.isEmpty()) {
return "";
}
// 下面继续验证是否包含开始标签,如果不包含,默认不是占位符,直接原样返回即可,否则继续执行。
int start = text.indexOf(openToken, 0);
if (start == -1) {
return text;
}
// 把text转成字符数组src,并且定义默认偏移量offset=0、存储最终需要返回字符串的变量builder,
// text变量中占位符对应的变量名expression。判断start是否大于-1(即text中是否存在openToken),如果存在就执行下面代码
char[] src = text.toCharArray();
int offset = 0;
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null;
while (start > -1) {
// 判断如果开始标记前如果有转义字符,就不作为openToken进行处理,否则继续处理
if (start > 0 && src[start - 1] == "\") {
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
//重置expression变量,避免空指针或者老数据干扰。
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {////存在结束标记时
if (end > offset && src[end - 1] == "\") {//如果结束标记前面有转义字符时
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {//不存在转义字符,即需要作为参数进行处理
expression.append(src, offset, end - offset);
offset = end + closeToken.length();
break;
}
}
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
//首先根据参数的key(即expression)进行参数处理,返回?作为占位符
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
}
- 创建BoundSql类存储处理后的sql和参数对
package com.customer.executor;
import com.customer.util.ParameterMapping;
import java.util.List;
public class BoundSql {
private String parseSql;
private List<ParameterMapping> parameterMappingList;
public BoundSql(String parseSql, List<ParameterMapping> parameterMappingList) {
this.parseSql = parseSql;
this.parameterMappingList = parameterMappingList;
}
public String getParseSql() {
return parseSql;
}
public void setParseSql(String parseSql) {
this.parseSql = parseSql;
}
public List<ParameterMapping> getParameterMappingList() {
return parameterMappingList;
}
public void setParameterMappingList(List<ParameterMapping> parameterMappingList) {
this.parameterMappingList = parameterMappingList;
}
}
1.4 简单测试
package com.testcus.test;
import com.customer.config.Resources;
import com.customer.sqlsession.SqlSession;
import com.customer.sqlsession.SqlSessionFactory;
import com.customer.sqlsession.SqlSessionFactoryBuilder;
import com.testcus.pojo.User;
import org.junit.Test;
import java.io.InputStream;
import java.util.List;
public class MyTest {
@Test
public void Test() throws Exception {
InputStream resourceAsSteam = Resources.getResourcesAsStream("mapperConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);
SqlSession sqlSession = sqlSessionFactory.openSession();
List<User> userList = sqlSession.selectList("user.selectList");
for (User user : userList) {
System.out.println(user);
}
}
}