Mybatis学习(一):基础概念和简单自定义持久层框架demo
< 返回列表时间: 2020-07-24来源:OSCHINA
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); } } }
热门排行