Mybatis
MyBatis官网地址:http://www.mybatis.org/mybatis-3/
第一部分 自定持久层框架
1.1 初始化数据库
-- 用户表和记录 SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for user -- ---------------------------- DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `password` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `birthday` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of user -- ---------------------------- INSERT INTO `user` VALUES (1, 'lucy', '123', '2019-12-12'); INSERT INTO `user` VALUES (2, 'tom', '123', '2019-12-12'); INSERT INTO `user` VALUES (3, 'jack', '123456', '2020-12-02'); SET FOREIGN_KEY_CHECKS = 1;
1.2 分析JDBC操作问题
package com.topxin.test; import com.topxin.pojo.User; import java.sql.*; public class Main { public static void main(String[] args) throws SQLException { Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try{ //1.获取驱动 Class.forName("com.mysql.jdbc.Driver"); //2.通过驱动获取数据库连接 connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/zdy_mybatis?characterEncoding=utf-8","root","root"); //3.定义sql语句 String sql = "select * from user where username = ?"; //4.获取预处理statement对象 preparedStatement = connection.prepareStatement(sql); //5.设置参数 preparedStatement.setString(1,"tom"); //6.查询 resultSet = preparedStatement.executeQuery(); //7.遍历结果集合 while (resultSet.next()){ int id = resultSet.getInt("id"); String username = resultSet.getString("username"); //8.封装user User user = new User(); user.setId(id); user.setUsername(username); System.out.println(user); } }catch (Exception e){ e.printStackTrace(); }finally { //9.释放资源 if (resultSet != null) { resultSet.close(); } if(preparedStatement != null){ preparedStatement.close(); } if(connection != null){ connection.close(); } } } }
JDBC问题总结:
原始jdbc开发存在的问题如下:
1、 数据库连接创建、释放频繁造成系统资源浪费,从而影响系统性能。
2、 Sql语句在代码中硬编码,造成代码不易维护,实际应用中sql变化的可能较大,sql变动需要改变java代码。
3、 使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。
4、 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便
1.3 问题解决思路
①使用数据库连接池初始化连接资源
②将sql语句抽取到xml配置文件中
③使用反射、内省等底层技术,自动将实体与表进行属性与字段的自动映射
1.4 自定义持久层框架设计思路
使用端:
提供核心配置文件:
sqlMapConfig.xml : 存放数据源信息,引入mapper.xml
Mapper.xml : sql语句的配置文件信息
框架端:
1.读取配置文件
读取完成以后以流的形式存在,我们不能将读取到的配置信息以流的形式存放在内存中,不好操作,可
以创建javaBean来存储
(1)Configuration : 存放数据库基本信息、Map<唯一标识,Mapper> 唯一标识:namespace + "."+ id
(2)MappedStatement:sql语句、statement类型、输入参数java类型、输出参数java类型
2.解析配置文件
创建sqlSessionFactoryBuilder类:
方法:sqlSessionFactory build():
第一:使用dom4j解析配置文件,将解析出来的内容封装到Configuration和MappedStatement中
第二:创建SqlSessionFactory的实现类DefaultSqlSession
3.创建SqlSessionFactory:
方法:openSession() : 获取sqlSession接口的实现类实例对象
4.创建sqlSession接口及实现类:主要封装crud方法
方法:selectList(String statementId,Object param):查询所有
selectOne(String statementId,Object param):查询单个
具体实现:封装JDBC完成对数据库表的查询操作
涉及到的设计模式:
Builder构建者设计模式、工厂模式、代理模式
1.5 自定义框架实现
1.4.1 pom.xml
<dependencies> <!--需要操作数据库--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.17</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.8</version> </dependency> <!--测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!--解析XML配置文件--> <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> </dependencies>
1.4.2 配置文件(核心)
sqlMapperConfig.xml
<configuration> <!--数据配置信息--> <dataSource> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/zdy_mybatis?characterEncoding=utf-8"></property> <property name="username" value="root"></property> <property name="password" value="root"></property> </dataSource> <!-- 存放mapper.xml的路径 --> <mapper resource="UserMapper.xml"></mapper> </configuration>
UserMapper.xml
<mapper namespace="com.topxin.dao.IUserDao"> <!-- sql 的唯一标识:namespace.id组成:statementId--> <select id="findAll" resultType="com.topxin.pojo.User"> select * from user </select> <!-- User user = new User(); user.setId(1); user.setUsernmae("zhangsan"); --> <select id="findByCondition" resultType="com.topxin.pojo.User" parameterType="com.topxin.pojo.User"> select * from user where id = #{id} and username = #{username} </select> </mapper>
1.4.3 创建实体
Configuration
public class Configuration { //连接数据库信息封装 private DataSource dataSource; //key:statementId value:封装好的mappedStatement对象 Map<String,MapperStatement> mapperStatementMap = new HashMap<String, MapperStatement>(); ...get/set }
MapperStatement
public class MapperStatement { private String id; //id标识 private String resultType; //返回值类型 private String parameterType; //参数值类型 private String sql; //sql语句 ...get/set }
User
public class User { private Integer id; private String username; private String password; private String birthday; ...get/set }
1.4.4 读取解析封装
Resource
读取:Resource获取配置文件流
package com.topxin.io; import java.io.InputStream; public class Resource { //根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中 public static InputStream getResourceAsSteam(String path){ InputStream resourceAsStream = Resource.class.getClassLoader().getResourceAsStream(path); return resourceAsStream; } }
XMlConfigBuilder
解析:将SqlMapperConfig.xml配置文件通过dom4j解析
封装:Configuration对象
注意:Configuration对象同时封装MapperStatement
package com.topxin.config; import com.mchange.v2.c3p0.ComboPooledDataSource; import com.topxin.io.Resource; import com.topxin.pojo.Configuration; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import java.beans.PropertyVetoException; import java.io.InputStream; import java.util.List; import java.util.Properties; public class XMlConfigBuilder { private Configuration configuration; public XMlConfigBuilder() { this.configuration = new Configuration(); } /** * 该方法即使将配置文件进行解析,封装Configuration * @return */ public Configuration parseConfig(InputStream inputStream) throws PropertyVetoException, DocumentException { Document document = new SAXReader().read(inputStream); //configuration Element rootElement = document.getRootElement(); Properties properties = new Properties(); List<Element> list = rootElement.selectNodes("//property"); for (Element element : list) { String name = element.attributeValue("name"); String value = element.attributeValue("value"); properties.setProperty(name,value); } ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource(); comboPooledDataSource.setDriverClass(properties.getProperty("driverClass")); comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl")); comboPooledDataSource.setUser(properties.getProperty("username")); comboPooledDataSource.setPassword(properties.getProperty("password")); configuration.setDataSource(comboPooledDataSource); //mapper.xml解析,拿到路径,根据路径---字节输入六---dom4j解析 List<Element> mapperList = rootElement.selectNodes("//mapper"); for (Element element : mapperList) { String mapperPath = element.attributeValue("resource"); InputStream resourceAsSteam = Resource.getResourceAsSteam(mapperPath); XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration); xmlMapperBuilder.parse(resourceAsSteam); } return configuration; } }
XMLMapperBuilder
解析:将UserMapper.xml配置文件通过dom4j解析
封装:MapperStatement对象
注意:根据 statementId的key = namespace +"."+ id
package com.topxin.config; import com.topxin.pojo.Configuration; import com.topxin.pojo.MapperStatement; 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 void parse(InputStream inputStream) throws DocumentException { Document document = new SAXReader().read(inputStream); Element rootElement = document.getRootElement(); String namespace = rootElement.attributeValue("namespace"); List<Element> list = rootElement.selectNodes("//select"); for (Element element : list) { String id = element.attributeValue("id"); String resultType = element.attributeValue("resultType"); String paramterType = element.attributeValue("parameterType"); String sqlText = element.getTextTrim(); MapperStatement mapperStatement = new MapperStatement(); mapperStatement.setId(id); mapperStatement.setResultType(resultType); mapperStatement.setParameterType(paramterType); mapperStatement.setSql(sqlText); String key = namespace +"."+ id; configuration.getMapperStatementMap().put(key,mapperStatement); } } }
1.4.5 SqlSessionFactory
核心方法openSession()
SqlSessionFactoryBuilder
package com.topxin.sqlSession; import com.topxin.config.XMlConfigBuilder; import com.topxin.pojo.Configuration; import org.dom4j.DocumentException; import java.beans.PropertyVetoException; import java.io.InputStream; public class SqlSessionFactoryBuilder { public SqlSessionFactory build(InputStream in) throws PropertyVetoException, DocumentException { //第一:使用dom4j解析配置文件,将解析出来的内容防撞到Configuration中 XMlConfigBuilder xMlConfigBuilder = new XMlConfigBuilder(); Configuration configuration = xMlConfigBuilder.parseConfig(in); //第二:创建sqlSessionFactory对象,工厂类,生产sqlSession会话对象 DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration); return defaultSqlSessionFactory; } }
SqlSessionFactory接口
package com.topxin.sqlSession; public interface SqlSessionFactory { public SqlSession openSession(); }
SqlSessionFactory接口的实现类DefaultSqlSessionFactory
package com.topxin.sqlSession; import com.topxin.pojo.Configuration; public class DefaultSqlSessionFactory implements SqlSessionFactory{ private Configuration configuration; public DefaultSqlSessionFactory(Configuration configuration) { this.configuration = configuration; } @Override public DefaultSqlSession openSession() { return new DefaultSqlSession(configuration); } }
1.4.6 SqlSession
invoke()方法
SqlSession接口
package com.topxin.sqlSession; import java.util.List; public interface SqlSession { //查询所有 public <E> List<E> selectList(String statementId,Object... params) throws Exception; //根据条件查询单个 public <T> T selectOne(String statementId,Object... params) throws Exception; //为Dao接口生产代理实现类 public <T> T getMapper(Class<?> mapperClass); }
SqlSession接口的实现类
package com.topxin.sqlSession; import com.topxin.pojo.Configuration; import com.topxin.pojo.MapperStatement; import java.lang.reflect.*; import java.util.List; public class DefaultSqlSession implements SqlSession { private Configuration configuration; public DefaultSqlSession(Configuration configuration){ this.configuration = configuration; } @Override public <E> List<E> selectList(String statementId, Object... params) throws Exception { //SimpleExecutor里的query方法调用 SimpleExecutor simpleExecutor = new SimpleExecutor(); MapperStatement mapperStatement = configuration.getMapperStatementMap().get(statementId); List<Object> list = simpleExecutor.query(configuration, mapperStatement, params); return (List<E>) list; } @Override public <T> T selectOne(String statementId, Object... params) throws Exception { List<Object> objects = selectList(statementId, params); if(objects.size() == 1){ return (T) objects.get(0); }else { throw new RuntimeException("查询结果未空或者返回多条结果"); } } @Override public <T> T getMapper(Class<?> mapperClass) { //使用JDK动态代理来为Dao接口涩会给你从代理对象,并返回 Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() { /** * 代理对象调用接口任何方法,都会执行invoke方法 * @param proxy 当前代理对象的应用 * @param method 当前被调用的方法引用 * @param args 传递的参数(user) * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //注意:底层还是去调用JDBC代码//根据不同情况,来调用selectList或者SelectOne //准备参数1:statementId:sql语句的唯一标识:namespace.id = 接口权限定名。方法名 //方法名 String methodName = method.getName(); String className = method.getDeclaringClass().getName(); String statementId = className +"."+ methodName; //准备参数 params:args //获取被调用方法的反回值类型 Type genericReturnType = method.getGenericReturnType(); //判断是否进行了,泛型类型参数化 if(genericReturnType instanceof ParameterizedType){ List<Object> objects = selectList(statementId, args); return objects; } return selectOne(statementId,args); } }); return (T) proxyInstance; } }
1.4.7 Executor
Executor接口
package com.topxin.sqlSession; import com.topxin.pojo.Configuration; import com.topxin.pojo.MapperStatement; import java.sql.SQLException; import java.util.List; public interface Executor { public <E> List<E> query(Configuration configuration, MapperStatement mapperStatement, Object... params) throws SQLException, Exception; }
BoundSql封装解析后的Sql语句
package com.topxin.sqlSession; import com.topxin.utils.ParameterMapping; import java.util.ArrayList; import java.util.List; public class BoundSql { private String sqlText; //解析过后的sql private List<ParameterMapping> parameterMappings = new ArrayList<>(); public BoundSql(String sqlText, List<ParameterMapping> parameterMappings) { this.sqlText = sqlText; this.parameterMappings = parameterMappings; } public String getSqlText() { return sqlText; } public void setSqlText(String sqlText) { this.sqlText = sqlText; } public List<ParameterMapping> getParameterMappings() { return parameterMappings; } public void setParameterMappings(List<ParameterMapping> parameterMappings) { this.parameterMappings = parameterMappings; } }
SimpleExecutor类实现了Executor接口
package com.topxin.sqlSession; import com.topxin.pojo.Configuration; import com.topxin.pojo.MapperStatement; import com.topxin.utils.GenericTokenParser; import com.topxin.utils.ParameterMapping; import com.topxin.utils.ParameterMappingTokenHandler; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.util.ArrayList; import java.util.List; public class SimpleExecutor implements Executor { @Override public <E> List<E> query(Configuration configuration, MapperStatement mapperStatement, Object... params) throws Exception { //1.获取连接 Connection connection = configuration.getDataSource().getConnection(); //2,获取sql语句,还需要#{}进行转换? String sql = mapperStatement.getSql(); BoundSql boundSql = getBoundSql(sql); //3/获取预处理对象 PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText()); //4.设置参数 String parameterType = mapperStatement.getParameterType();//获取到参数 Class<?> parameterTypeClass = getClassType(parameterType); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); for (int i = 0; i < parameterMappings.size(); i++) { ParameterMapping parameterMapping = parameterMappings.get(i); String content = parameterMapping.getContent(); //反射 Field declaredField = parameterTypeClass.getDeclaredField(content); declaredField.setAccessible(true); Object o = declaredField.get(params[0]); preparedStatement.setObject(i+1,o); //占位符从1开始 } //5.执行sql ResultSet resultSet = preparedStatement.executeQuery(); String resultType = mapperStatement.getResultType(); Class<?> resultTypeClass = getClassType(resultType); ArrayList<Object> objects = new ArrayList<>(); //6.分装返回结果集 while(resultSet.next()){ Object o = resultTypeClass.newInstance(); //元数据 ResultSetMetaData metaData = resultSet.getMetaData(); for (int i = 1; i <= metaData.getColumnCount(); i++) { //字段名称 String columnName = metaData.getColumnName(i); //字段的值 Object value = resultSet.getObject(columnName); //使用反射,根据数据库表和实体的对象关系完成封装 PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass); Method writeMethod = propertyDescriptor.getWriteMethod(); writeMethod.invoke(o,value); } objects.add(o); } return (List<E>) objects; } private Class<?> getClassType(String parameterType) throws ClassNotFoundException { if(parameterType != null){ Class<?> aClass = Class.forName(parameterType); return aClass; } return null; } /** * 完成对#{}的解析共工作,1将#{}使用?,进行代替,2解析#{}里面的 * @param sql * @return */ private BoundSql getBoundSql(String sql) { ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler(); GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler); //解析后的sql String parseSql = genericTokenParser.parse(sql); //#{}里面解析出来的参数名称 List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings(); BoundSql boundSql = new BoundSql(parseSql,parameterMappings); return boundSql; } }
1.4.8 Utils
GenericTokenParser解析Mybaitis中Sql语句的${}和#{}
/** * 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.topxin.utils; /** * @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(); } }
ParameterMapping分装Mybaitis中Sql语句的${}和#{}的内容
package com.topxin.utils; 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; } }
TokenHandler接口
/** * Copyright 2009-2015 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.topxin.utils; /** * @author Clinton Begin */ public interface TokenHandler { String handleToken(String content); }
ParameterMappingTokenHandler类
package com.topxin.utils; import java.util.ArrayList; import java.util.List; public class ParameterMappingTokenHandler implements TokenHandler { private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>(); // context是参数名称 #{id} #{username} 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; } }
1.4.9 Test测试
package com.topxin.test; import com.topxin.dao.IUserDao; import com.topxin.io.Resource; import com.topxin.pojo.User; import com.topxin.sqlSession.SqlSession; import com.topxin.sqlSession.SqlSessionFactory; import com.topxin.sqlSession.SqlSessionFactoryBuilder; import org.junit.Test; import java.io.InputStream; import java.util.List; public class IPersistenceTest { @Test public void test() throws Exception { InputStream resourceAsSteam = Resource.getResourceAsSteam("sqlMapperConfig.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam); SqlSession sqlSession = sqlSessionFactory.openSession(); //调用 User user = new User(); user.setId(1); user.setUsername("lucy"); /* User u = sqlSession.selectOne("user.selectOne", user); System.out.println(u);*/ /*List<User> users = sqlSession.selectList("user.selectList"); for (User u : users) { System.out.println(u); }*/ //返回的userDao是代理对象proxy IUserDao userDao = sqlSession.getMapper(IUserDao.class); /*User byCondition = userDao.findByCondition(user); System.out.println(byCondition);*/ List<User> all = userDao.findAll(); for (User user1 : all) { System.out.println(user1); } } }
1.6 自定义框架优化
通过上述我们的自定义框架,我们解决了JDBC操作数据库带来的一些问题:例如频繁创建释放数据库连接,硬编码,手动封装返回结果集等问题,但是现在我们继续来分析刚刚完成的自定义框架代码,有没有什么问题?
问题如下:
-
dao的实现类中存在重复的代码,整个操作的过程模板重复(创建sqlsession,调用sqlsession方法,关闭 sqlsession)
-
dao的实现类中存在硬编码,调用sqlsession的方法时,参数statement的id硬编码
解决:使用代理模式来创建接口的代理对象
@Test public void test2() throws Exception { InputStream resourceAsSteam = Resources.getResourceAsSteam(path: "sqlMapConfig.xml") SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsSteam); SqlSession sqlSession = build.openSession(); User user = new User(); user.setld(l); user.setUsername("tom"); //代理对象 UserMapper userMapper = sqlSession.getMappper(UserMapper.class); User userl = userMapper.selectOne(user); System・out.println(userl); }
在sqlSession中添加方法
public interface SqlSession { public <T> T getMappper(Class<?> mapperClass);}
实现类
@Override public <T> T getMappper(Class<?> mapperClass) { T o = (T) Proxy.newProxyInstance(mapperClass.getClassLoader(), new Class[] {mapperClass}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // selectOne String methodName = method.getName(); // className:namespace String className = method.getDeclaringClass().getName(); //statementid String key = className+"."+methodName; MappedStatement mappedStatement = configuration.getMappedStatementMap().get(key); Type genericReturnType = method.getGenericReturnType(); ArrayList arrayList = new ArrayList<> (); //判断是否实现泛型类型参数化 if(genericReturnType instanceof ParameterizedType){ return selectList(key,args); return selectOne(key,args); } }); return o; }
第二部分 Mybatis相关概念
2.1 对象/关系数据映射ORM
Object Relation Mapping
既可以利用面向对象程序设计语言的简单易用性,又可以利用关系数据库的技术优势
2.2 Mybatis简介
ORM、半自动、轻量级框架
支持定制化sql,存储过程,高级映射
xml或者注解配置和映射原生类
2.3 Mybaits历史
原是apache的一个开源项目iBatis, 2010年6月这个项目由apache software foundation 迁移到了
google code,随着开发团队转投Google Code旗下,ibatis3.x正式更名为Mybatis ,代码于2013年11
月迁移到Github。
iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框
架包括SQL Maps和Data Access Objects(DAO)
2.4 Mybaits优势
半自动的持久层框架(轻量级),核心Sql可以自己优化
sql和Java代码分离(功能边界清晰,一个专注业务,一个专注数据)
第三部分 Mybatis基础应用
3.1 快速入门
3.1.1 导入GAV坐标
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.5</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.46</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies>
3.1.2 创建数据库表
user表,创建语句见1.1
3.1.3 创建对应实体
User
3.1.4 编写映射文件
UserMapper.xml
3.1.5 编写核心配置文件
SqlMapConfig.xml
<?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> <!--应用启动,解析配置文件生产Configuration配置对象 environments:default属性选择项目当前数据源值就是environment的id environment数据库源,可以配置多个--> <environments default="development"> <environment id="development"> <transactionManager type="JDBC" /><!--事务管理JDBC--> <dataSource type="POOLED"><!--mybatis自带--> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://127.0.0.1:3306/zdy_mybatis?characterEncoding=utf8" /> <property name="username" value="root" /> <property name="password" value="root" /> </dataSource> </environment> </environments> <mappers> <!--单个mapper配置,--> <mapper resource="UserMapper.xml"></mapper> </mappers> </configuration>
3.1.6 编写测试方法
@Test public void testMybatis() throws IOException { //加载核心配置文件,返回流 InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); //获取sqlSession工厂 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //获取sqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); List<User> list = sqlSession.selectList("userMapper.queryUserList"); if(list != null && list.size() > 0) { for (int i = 0; i < list.size(); i++) { User user = list.get(i); System.out.println(user); } } //关闭资源 sqlSession.close(); }
3.2 MyBatis常用配置解析
3.2.1 environments标签
其中,事务管理器(transactionManager)类型有两种:
-
JDBC:这个配置就是直接使用了JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域。
-
MANAGED:这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接,然而一些容器并不希望这样,因此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为。
其中,数据源(dataSource)类型有三种:
-
UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。
-
POOLED:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来。
-
JNDI:这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。
3.2.2 mapper标签
该标签的作用是加载映射的,加载方式有如下几种:
-
使用相对于类路径的资源引用,例如:
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
-
使用完全限定资源定位符(URL),例如:
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
-
使用映射器接口实现类的完全限定类名,例如:
<mapper class="org.mybatis.builder.AuthorMapper"/>
-
将包内的映射器接口实现全部注册为映射器,例如:
<package name="org.mybatis.builder"/>
3.3 Mybatis相关API介绍
SqlSession工厂构建器SqlSessionFactoryBuilder
常用API:SqlSessionFactory build(InputStream inputStream)
通过加载mybatis的核心文件的输入流的形式构建一个SqlSessionFactory对象
String resource = "org/mybatis/builder/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(inputStream);
其中, Resources 工具类,这个类在 org.apache.ibatis.io 包中。Resources 类帮助你从类路径下、文
件系统或一个 web URL 中加载资源文件。
SqlSession工厂对象SqlSessionFactory
SqlSessionFactory 有多个个方法创建SqlSession 实例。常用的有如下两个:
方法 | 解释 |
---|---|
openSession()会默认 | 会默认开启一个事务,但事物不会自动提交,也就意味需要手动提交改事物,更新操作数据库才会吃计划到数据库中 |
openSession(boolean autoCommit) | 参数是否自动提交,如果设置未true,那么不需要手动提交事务 |
执行语句的方法主要有:
<T> T selectOne(String statement, Object parameter) <E> List<E> selectList(String statement, Object parameter) int insert(String statement, Object parameter) int update(String statement, Object parameter) int delete(String statement, Object parameter)
操作事务的方法主要有:
void commit() void rollback()
3.4 Mybaits的Dao层实现
3.4.1 传统开发方式
3.4.2 代理开发方式
采用 Mybatis 的代理开发方式实现 DAO 层的开发,这种方式是我们后面进入企业的主流。
Mapper 接口开发方法只需要程序员编写Mapper 接口(相当于Dao 接口),由Mybatis 框架根据接口
定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。
Mapper 接口开发需要遵循以下规范:
1) Mapper.xml文件中的namespace与mapper接口的全限定名相同
2) Mapper接口方法名和Mapper.xml中定义的每个statement的id*相同
3) Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同
4) Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同
第四部分 配置文件深入
4.1 SqlMapConfiig.xml
4.1.1 配置
4.1.2 environments环境配置
事务管理器(transactionManager)
在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):
-
JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
-
MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。例如:
数据源(dataSource)
dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):
UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。
POOLED(推荐)– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。
JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。
4.1.3 mapper映射器
使用相对于类路径的资源引用
使用完全限定资源定位符(URL)
使用映射器接口实现类的完全限定类名
<!-- 将包内的映射器接口实现全部注册为映射器 --> <mappers> <package name="com.topxin.mapper"/> </mappers>
4.1.4 properties属性
<properties> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://127.0.0.1:3306/zdy_mybatis?characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="root"/> </properties>
从 MyBatis 3.4.2 开始,你可以为占位符指定一个默认值。例如:
<dataSource type="POOLED"> <!-- ... --> <property name="username" value="${username:ut_user}"/> <!-- 如果属性 'username' 没有被配置,'username' 属性的值将为 'ut_user' --> </dataSource>
4)typeAliases类型别名标签
<!-- 定义类型别名,单独指定 --> <typeAliases> <typeAlias alias="User" type="com.topxin.User"/> <typeAlias alias="Orders" type="com.topxin.Orders"/> </typeAliases>
<!-- 定义类型别名,扫描指定的包,这样扫描的别名默认是类名首字母小写,如果需要单独指定可以使用@Alias注解,使用方式如下: --> <typeAliases> <package name="com.topxin"/> </typeAliases>
// 扫描包方式自定义别名 @Alias("user") public class user {...}
别名 | 映射的类型 |
---|---|
_byte | byte |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
object | Object |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
iterator | Iterator |
4.2 Mapper.xml
4.2.1 动态sql
-
if
-
choose (when, otherwise)
-
trim (where, set)
-
foreach
-
sql
第五部分 Mybatis复杂映射
5.1 一对一
一个用户对应一个订单
SELECT * FROM orders o INNER JOIN USER u ON o.uid = u.id
resultMap:手动配置
association:一对一
<?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="com.topxin.mapper.OrdersMapper"> <!--自定义映射关系--> <resultMap id="orderAndUserResultMap" type="com.topxin.pojo.Orders"> <id property="id" column="id" /> <result property="ordertime" column="ordertime" /> <result property="total" column="total" /> <!-- user映射 --> <association property="user" javaType="com.topxin.pojo.User"> <result property="id" column="uid" /> <result property="username" column="username" /> <result property="password" column="password" /> <result property="birthday" column="birthday" /> </association> </resultMap> <!--一对一查询--> <select id="findOrderAndUser" resultMap="orderAndUserResultMap"> SELECT * FROM orders o INNER JOIN USER u ON o.uid = u.id </select> </mapper>
订单表初始化
-- 创建订单表并插入记录 SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for orders -- ---------------------------- DROP TABLE IF EXISTS `orders`; CREATE TABLE `orders` ( `id` int(11) NOT NULL AUTO_INCREMENT, `ordertime` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `total` double NULL DEFAULT NULL, `uid` int(11) NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE, INDEX `uid`(`uid`) USING BTREE, CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT ) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of orders -- ---------------------------- INSERT INTO `orders` VALUES (1, '2019-12-12', 3000, 1); INSERT INTO `orders` VALUES (2, '2019-12-12', 4000, 1); INSERT INTO `orders` VALUES (3, '2019-12-12', 5000, 2); SET FOREIGN_KEY_CHECKS = 1;
5.2 一对多
一个用户对应多个订单
SELECT * FROM USER u LEFT JOIN orders o ON u.id = o.uid
collection:一对多
<?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="com.topxin.mapper.UserMapper"> <resultMap id="userAndOrderResultMap" type="com.topxin.pojo.User"> <!-- 注意这里的user id属性值 --> <id property="id" column="uid" /> <result property="username" column="username" /> <result property="password" column="password" /> <result property="birthday" column="birthday" /> <collection property="ordersList" ofType="com.topxin.pojo.Orders"> <result property="id" column="id" /> <result property="ordertime" column="ordertime" /> <result property="total" column="total" /> </collection> </resultMap> <!-- 一对多查询 statement : namespacer.id = com.topxin.dao.UserMapper.findUserAndOrder--> <select id="findUserAndOrder" resultMap="userAndOrderResultMap"> SELECT * FROM USER u LEFT JOIN orders o ON u.id = o.uid </select> </mapper>
5.3 多对多
多个用户对应多个角色
-- 查询数据sql,对于多对多关系,都是通过中间表关联 SELECT * FROM USER u LEFT JOIN sys_user_role ur ON u.id = ur.userid INNER JOIN sys_role r ON ur.roleid = r.id;
<?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="com.topxin.mapper.UserMapper"> <resultMap id="userAndRoleResultMap" type="com.topxin.pojo.User"> <id property="id" column="id" /> <result property="username" column="username" /> <result property="password" column="password" /> <result property="birthday" column="birthday" /> <collection property="roleList" ofType="com.topxin.pojo.Role"> <result property="id" column="roleid" /> <result property="roleName" column="roleName" /> <result property="roleDesc" column="roleDesc" /> </collection> </resultMap> <select id="findUserAndRole" resultMap="userAndRoleResultMap"> SELECT * FROM USER u LEFT JOIN sys_user_role ur ON u.id = ur.userid INNER JOIN sys_role r ON ur.roleid = r.id; </select> </mapper>
初始话用户角色关系表和角色表
-- 创建角色表 SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for sys_role -- ---------------------------- DROP TABLE IF EXISTS `sys_role`; CREATE TABLE `sys_role` ( `id` int(11) NOT NULL AUTO_INCREMENT, `rolename` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `roleDesc` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of sys_role -- ---------------------------- INSERT INTO `sys_role` VALUES (1, 'CTO', 'CTO'); INSERT INTO `sys_role` VALUES (2, 'CEO', 'CEO'); SET FOREIGN_KEY_CHECKS = 1; -- ------------------------------------------------- -- 创建用户角色中间表 SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for sys_user_role -- ---------------------------- DROP TABLE IF EXISTS `sys_user_role`; CREATE TABLE `sys_user_role` ( `userid` int(11) NOT NULL, `roleid` int(11) NOT NULL, PRIMARY KEY (`userid`, `roleid`) USING BTREE, INDEX `roleid`(`roleid`) USING BTREE, CONSTRAINT `sys_user_role_ibfk_1` FOREIGN KEY (`userid`) REFERENCES `sys_role` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT, CONSTRAINT `sys_user_role_ibfk_2` FOREIGN KEY (`roleid`) REFERENCES `user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of sys_user_role -- ---------------------------- INSERT INTO `sys_user_role` VALUES (1, 1); INSERT INTO `sys_user_role` VALUES (2, 1); INSERT INTO `sys_user_role` VALUES (1, 2); INSERT INTO `sys_user_role` VALUES (2, 2); SET FOREIGN_KEY_CHECKS = 1;
5.4 知识小结
MyBatis多表配置方式:
一对一配置:使用做配置
一对多配置:使用+做配置
多对多配置:使用+做配置
第六部分 Mybatis注解开发
注意:多表复杂操作更适合xml
6.1 常用注解
注解 | 用途 | |
---|---|---|
@Insert | 新增 | |
@Update | 更新 | |
@Delete | 删除 | |
@Select | 查询 | select |
@Result | 封装结果集 | id和result |
@Results | 与@Result配合,封装多个结果集 | resultMap |
@One | 实现一对一结果集封装 | association |
@Many | 实现一对多结果集封装 | collection |
6.2 一对一
一个订单对应一个用户uid作为参数
// 用户查询 @Select(value = "select * from user where id = #{id}") User selectById(Integer id);
//一对一查询 @Results({ @Result(property = "id", column = "id"), @Result(property = "ordertime", column = "ordertime"), @Result(property = "total", column = "total"), @Result( javaType = User.class, property = "user", column = "uid", one = @One(select = "com.topxin.mapper.UserMapper.selectById")) }) @Select(value = "select * from orders") List<Orders> findAllOrdersAndUser();
6.3 一对多
一个用户对应多个订单
// select * from user u left join orders o on u.id = o.uid; @Results({ @Result(property = "id", column = "id"), @Result(property = "username", column = "username"), @Result(property = "password", column = "password"), @Result(property = "birthday", column = "birthday"), @Result( property = "orderList", column = "id", javaType = List.class, many = @Many(select = "com.topxin.mapper.OrdersMapper.selectByUId") ) }) @Select(value = "select * from user") List<User> findAllUserAndOrder();
// 根据用户id返回查询结果 @Select(value = "select * from orders where uid = #{uid}") List<Orders> selectByUId(Integer uid);
6.4 多对多
多个用户对应多个角色
//select * from user u left join sys_user_role ur on u.id = ur.userid left join sys_role r on ur.roleid = r.id; @Results({ @Result(property = "id", column = "id"), @Result(property = "username", column = "username"), @Result(property = "password", column = "password"), @Result(property = "birthday", column = "birthday"), @Result( property = "roleList", column = "id", javaType = List.class, many = @Many(select = "com.topxin.mapper.RoleMapper.selectByUserId") ) }) @Select(value = "select * from user") List<User> findAllUserAndRole();
@Select(value = "select r.* from sys_role r inner join sys_user_role ur on r.id = ur.roleid where userid = #{uid}") List<Role> selectByUserId(Integer uid);
6.5 注解动态sql
@Update({"<script>", "update Author", " <set>", " <if test='username != null'>username=#{username},</if>", " <if test='password != null'>password=#{password},</if>", " </set>", "where id=#{id}", "</script>"}) void updateUserValues(User user);
第七部分 Mybaits缓冲
mybatis使用了俩种缓冲,本地缓冲(local cache)和二级缓冲(second level cache)
7.1 一级缓冲
sqlSession(HashMap)
cachekey:statementId、params、boundSql、rowBounds组成
1)第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从 数据
库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。
2)如果中间sqlSession去执行commit操作(执行插入、更新、删除),则会清空SqlSession中的 一
级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
3)第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直 接从
缓存中获取用户信息
4)手动清除一级缓冲sqlSession.claerCache();
@Test public void localCacheTest(){ // 使用sqlSession执行第一次查询 List<User> u1 = userMapper.findUserAndOrder(); // 使用sqlSession执行第二次查询 List<User> u2 = userMapper.findUserAndOrder(); System.out.println(u1 == u2); //true sqlSession.close(); }
一级缓存原理探究与源码分析
一级缓存到底是什么?
一级缓存什么时候被创建?
一级缓存的工作流程是怎样的?
相信你现在应该会有这几个疑问,那么我们本节就来研究一下一级缓存的本质大家可以这样想,上面我们一直提到一级缓存,那么提到一级缓存就绕不开SqlSession,所以索性我们就直接从SqlSession,看看有没有创建缓存或者与缓存有关的属性或者方法
调研了一圈,发现上述所有方法中,好像只有clearCache()和缓存沾点关系,那么就直接从这个方法入手吧,分析源码时,我们要看它(此类)是谁,它的父类和子类分别又是谁,对如上关系了解了,你才会对这个类有更深的认识,分析了一圈,你可能会得到如下这个流程图
再深入分析,流程走到Perpetualcache中的clear()方法之后,会调用其cache.clear()方法,那么这个cache是什么东西呢?点进去发现,cache其实就是private Map cache = new HashMap();也就是一个Map,所以说cache.clear()其实就是map.clear(),也就是说,缓存其实就是本地存放的一个map对象,每一个SqISession都会存放一个map对象的引用,那么这个cache是何时创建的呢?
你觉得最有可能创建缓存的地方是哪里呢?我觉得是Executor,为什么这么认为?因为Executor是 执行器,用来执行SQL请求,而且清除缓存的方法也在Executor中执行,所以很可能缓存的创建也很 有可能在Executor中,看了一圈发现Executor中有一个createCacheKey方法,这个方法很像是创 建缓存的方法啊,跟进去看看,你发现createCacheKey方法是由BaseExecutor执行的,代码如下
CacheKey cacheKey = new CacheKey(); //MappedStatement 的 id // id就是Sql语句的所在位置包名+类名+ SQL名称 cacheKey.update(ms.getId()); // offset 就是 0 cacheKey.update(rowBounds.getOffset()); // limit 就是 Integer.MAXVALUE cacheKey.update(rowBounds.getLimit()); //具体的SQL语句 cacheKey.update(boundSql.getSql()); //后面是update 了 sql中带的参数 cacheKey.update(value); ... if (configuration.getEnvironment() != null) { // issue #176 cacheKey.update(configuration.getEnvironment().getId());
创建缓存key会经过一系列的update方法,udate方法由一个CacheKey这个对象来执行的,这个update方法最终由updateList的list来把五个值存进去,对照上面的代码和下面的图示,你应该能 理解这五个值都是什么了
这里需要注意一下最后一个值,configuration.getEnvironment().getId()这是什么,这其实就是 定义在mybatis-config.xml中的标签,见如下。
<environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments>
那么我们回归正题,那么创建完缓存之后该用在何处呢?总不会凭空创建一个缓存不使用吧?绝对不会的,经过我们对一级缓存的探究之后,我们发现一级缓存更多是用于查询操作,毕竟一级缓存也叫做查询缓存吧,为什么叫查询缓存我们一会儿说。我们先来看一下这个缓存到底用在哪了,我们跟踪到query方法如下:
//此方法在SimpleExecutor的父类BaseExecutor中实现 @Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { //根据传入的参数动态获得SQL语句,最后返回用BoundSql对象表示 BoundSql boundSql = ms.getBoundSql(parameter); //为本次查询创建缓存的Key CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); // 查询 return query(ms, parameter, rowBounds, resultHandler, key, boundSql); } List<E> list; try { // queryStack + 1 queryStack++; // 从一级缓存中,获取查询结果 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; // 获取到,则进行处理 if (list != null) { handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); // 获得不到,则从数据库中查询 } else { list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } // 从数据库中读取操作 private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> list; // 在缓存中,添加占位对象。此处的占位符,和延迟加载有关,可见 `DeferredLoad#canLoad()` 方法 localCache.putObject(key, EXECUTION_PLACEHOLDER); try { // 执行读操作 list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { // 从缓存中,移除占位对象 localCache.removeObject(key); } // 添加到缓存中 localCache.putObject(key, list); // 暂时忽略,存储过程相关 if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list; }
如果查不到的话,就从数据库查,在queryFromDatabase中,会对localcache进行写入。 localcache对象的put方法最终交给Map进行存放
private Map<Object, Object> cache = new HashMap<Object, Object>(); @Override public void putObject(Object key, Object value) { cache.put(key, value); }
7.2 二级缓存
开启二级缓冲
和一级缓存默认开启不一样,二级缓存需要我们手动开启
首先在全局配置文件sqlMapConfig.xml文件中加入如下代码:
<!--开启second level cache,local cache默认开启--> <settings> <setting name="cacheEnabled" value="true"/> </settings>
其次在UserMapper.xml文件中开启缓存
<!--开启二级缓存--> <cache></cache>
我们可以看到mapper.xml文件中就这么一个空标签,其实这里可以配置,PerpetualCache这个类是mybatis默认实现缓存功能的类。我们不写type就使用mybatis默认的缓存,也可以去实现Cache接口来自定义缓存。
public class PerpetualCache implements Cache { private final String id; private MapcObject, Object> cache = new HashMapC); public PerpetualCache(St ring id) {this.id = id;} }
我们可以看到二级缓存底层还是HashMap结构
public class User implements Serializable( //用户ID private int id; //用户姓名 private String username; //用户性别 private String sex; }
开启了二级缓存后,还需要将要缓存的pojo实现Serializable接口,为了将缓存数据取出执行反序列化操 作,因为二级缓存数据存储介质多种多样,不一定只存在内存中,有可能存在硬盘中,如果我们要再取 这个缓存的话,就需要反序列化了。所以mybatis中的pojo都去实现Serializable接口
测试
测试二级缓存和sqlSession无关
@Test public void secondLevelCacheTest(){ SqlSession sqlSession1 = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); SqlSession sqlSession3 = sqlSessionFactory.openSession(); UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class); UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class); UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class); List<User> u1 = mapper1.findUserAndOrder(); sqlSession1.close(); List<User> u2 = mapper2.findUserAndOrder(); System.out.println(u1 == u2);//false不是存储的地址 }
useCache和flushCache
mybatis中还可以配置userCache和flushCache等配置项,userCache是用来设置是否禁用二级缓 存的,在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出 sql去查询,默认情况是true,即该sql使用二级缓存
<select id="selectUserByUserId" useCache="false" resultType="com.lagou.pojo.User" parameterType="int"> select * from user where id=#{id} </select>
这种情况是针对每次查询都需要最新的数据sql,要设置成useCache=false,禁用二级缓存,直接从数 据库中获取。在mapper的同一个namespace中,如果有其它insert、update, delete操作数据后需要刷新缓 存,如果不执行刷新缓存会出现脏读。设置statement配置中的flushCache="true”属性,默认情况下为true,即刷新缓存,如果改成false则 不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。
<select id="selectUserByUserId" flushCache="true" useCache="false" resultType="com.lagou.pojo.User" parameterType="int"> select * from user where id=#{id} </select>
一般下执行完commit操作都需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏读。所以我们不用设置,默认即可
7.3 二级缓存整合redis
mybaits本身二级缓冲不支持分布式。
7.3.1 pom
<dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-redis</artifactId> <version>1.0.0-beta2</version> </dependency>
7.3.2 redis.properties
redis.host=localhost redis.port=6379 redis.connectionTimeout=5000 redis.password= redis.database=0
7.3.3 UserMapper.xml
<cache type="org.mybatis.caches.redis.RedisCache" /> <!-- 一对多查询 statement : namespacer.id com.topxin.dao.UserMapper.findUserAndOrder--> <select id="findUserAndOrder" resultMap="userAndOrderResultMap" useCache="true"> SELECT * FROM USER u LEFT JOIN orders o ON u.id = o.uid </select>
7.3.4 Test
@Test public void secondLevelCacheTest(){ SqlSession sqlSession1 = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); SqlSession sqlSession3 = sqlSessionFactory.openSession(); UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class); UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class); UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class); sqlSession.close(); //关闭sqlSession1测试Redis缓冲是否成功 List<User> u1 = mapper1.findUserAndOrder(); sqlSession1.close(); //调用tmc中的commit()方法将map中的事务放入2级缓冲对象中 List<User> u2 = mapper2.findUserAndOrder(); System.out.println(u1 == u2); //false不是存储的地址 }
7.3.5 源码分析
RedisCache和大家普遍实现Mybatis的缓存方案大同小异,无非是实现Cache接口,并使用jedis操作缓存;不过该项目在设计细节上有一些区别;
public final class RedisCache implements Cache { private final ReadWriteLock readWriteLock = new DummyReadWriteLock(); private String id; private static JedisPool pool; public RedisCache(String id) { if (id == null) { throw new IllegalArgumentException("Cache instances require an ID"); } else { this.id = id; RedisConfig redisConfig = RedisConfigurationBuilder.getInstance().parseConfiguration(); pool = new JedisPool(redisConfig, redisConfig.getHost(), redisConfig.getPort(), redisConfig.getConnectionTimeout(), redisConfig.getSoTimeout(), redisConfig.getPassword(), redisConfig.getDatabase(), redisConfig.getClientName()); } } }
RedisCache在mybatis启动的时候,由MyBatis的CacheBuilder创建,创建的方式很简单,就是调用RedisCache的带有String参数的构造方法,即RedisCache(String id);而在RedisCache的构造方法中,调用了 RedisConfigu rationBuilder 来创建 RedisConfig 对象,并使用 RedisConfig 来创建JedisPool。
RedisConfig类继承了 JedisPoolConfig,并提供了 host,port等属性的包装,简单看一下RedisConfig的属性:
public class RedisConfig extends JedisPoolConfig { private String host = "localhost"; private int port = 6379; private int connectionTimeout = 2000; private int soTimeout = 2000; private String password; private int database = 0; private String clientName; }
RedisConfig对象是由RedisConfigurationBuilder创建的,简单看下这个类的主要方法:
public RedisConfig parseConfiguration(ClassLoader classLoader) { Properties config = new Properties(); InputStream input = classLoader.getResourceAsStream(this.redisPropertiesFilename); if (input != null) { try { config.load(input); } catch (IOException var12) { throw new RuntimeException("An error occurred while reading classpath property '" + this.redisPropertiesFilename + "', see nested exceptions", var12); } finally { try { input.close(); } catch (IOException var11) { } } } RedisConfig jedisConfig = new RedisConfig(); this.setConfigProperties(config, jedisConfig); return jedisConfig; }
核心的方法就是parseConfiguration方法,该方法从classpath中读取一个redis.properties文件:
redis.host=localhost redis.port=6379 redis.connectionTimeout=5000 redis.password= redis.database=0
并将该配置文件中的内容设置到RedisConfig对象中,并返回;接下来,就是RedisCache使用RedisConfig类创建完成edisPool;在RedisCache中实现了一个简单的模板方法,用来操作Redis:
private Object execute(RedisCallback callback) { Jedis jedis = pool.getResource(); Object var3; try { var3 = callback.doWithRedis(jedis); } finally { jedis.close(); } return var3; }
模板接口为RedisCallback,这个接口中就只需要实现了一个doWithRedis方法而已:
public interface RedisCallback { Object doWithRedis(Jedis jedis); }
接下来看看Cache中最重要的两个方法:putObject和getObject,通过这两个方法来查看mybatis-redis储存数据的格式
public void putObject(final Object key, final Object value) { this.execute(new RedisCallback() { public Object doWithRedis(Jedis jedis) { jedis.hset(RedisCache.this.id.toString().getBytes(), key.toString().getBytes(), SerializeUtil.serialize(value)); return null; } }); } public Object getObject(final Object key) { return this.execute(new RedisCallback() { public Object doWithRedis(Jedis jedis) { return SerializeUtil.unserialize(jedis.hget(RedisCache.this.id.toString().getBytes(), key.toString().getBytes())); } }); }
可以很清楚的看到,mybatis-redis在存储数据的时候,是使用的hash结构,把cache的id作为这个hash 的key (cache的id在mybatis中就是mapper的namespace);这个mapper中的查询缓存数据作为hash的field,需要缓存的内容直接使用SerializeUtil存储,SerializeUtil和其他的序列化类差不多,负责对象的序列化和反序列化;
第八部分 Mybatis插件
8.1 插件原理
一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展。这样的好处是显而易见的,一是增加了框架的灵活性。二是开发者可以结合实际需求,对框架进行拓展,使其能够更好的工作。以MyBatis为例,我们可基于MyBati s插件机制实现分页、分表,监控等功能。由于插件和业务 无关,业务也无法感知插件的存在。因此可以无感植入插件,在无形中增强功能
8.2 Mybatis插件介绍
Mybati s作为一个应用广泛的优秀的ORM开源框架,这个框架具有强大的灵活性,在四大组件(Executor、StatementHandler、ParameterHandler、ResultSetHandler)处提供了简单易用的插 件扩展机制。Mybatis对持久层的操作就是借助于四大核心对象。MyBatis支持用插件对四大核心对象进 行拦截,对mybatis来说插件就是拦截器,用来增强核心对象的功能,增强功能本质上是借助于底层的 动态代理实现的,换句话说,MyBatis中的四大对象都是代理对象
MyBatis所允许拦截的方法如下
-
执行器Executor (update、query、commit、rollback等方法);
-
SQL语法构建器StatementHandler (prepare、parameterize、batch、updates query等方 法);
-
参数处理器ParameterHandler (getParameterObject、setParameters方法);
-
结果集处理器ResultSetHandler (handleResultSets、handleOutputParameters等方法);
8.3 插件原理
在四大对象创建的时候
-
每个创建出来的对象不是直接返回的,而是interceptorChain.pluginAll(parameterHandler);
-
获取到所有的Interceptor (拦截器)(插件需要实现的接口);调用 interceptor.plugin(target);返
回 target 包装后的对象
-
插件机制,我们可以使用插件为目标对象创建一个代理对象;AOP (面向切面)我们的插件可 以
为四大对象创建出代理对象,代理对象就可以拦截到四大对象的每一个执行;
拦截
插件具体是如何拦截并附加额外的功能的呢?以ParameterHandler来说
8.3 自定义插件
8.4 插件源码分析
8.5 pageHelper分页插件
8.5.1 坐标
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>3.7.5</version> </dependency> <dependency> <groupId>com.github.jsqlparser</groupId> <artifactId>jsqlparser</artifactId> <version>0.9.1</version> </dependency>
配置
8.6 通用mapper
坐标
配置
映射
第九部分 架构原理
9.1 架构设计
9.3 工作流程
第十部门 源码剖析
10.1 传统方式源码剖析
10.1.1 源码剖析初始化
Test类方法读取配置文件,加载文件流
//1.读取字节文件,转换蔚输入流,注意现在还没有解析 InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); //2.解析配置文件,封装Configuration对象,创建DefaultSqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
进入SqlSessionFactoryBuilder类调用build()的重载方法
// 2.调用的重载方法 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { // 创建 XMLConfigBuilder, XMLConfigBuilder是专门解析mybatis的配置文件的类 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // 执行 XML 解析 // 创建 DefaultSqlSessionFactory 对象 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. } } }
进入XMLConfigBuilder的parse()方法解析xml并封装对象Configuration,Configuration对象属性与Xml配置是对应的
注意:在Myabtis初始化中会将Myabatis配置文件中的信息全部加载到内存中,使用
org.apache.ibatis.session.Configuration 实例来维护
/** * 解析 XML 成 Configuration 对象。 * * @return Configuration 对象 */ public Configuration parse() { // 若已解析,抛出 BuilderException 异常 if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } // 标记已解析 parsed = true; ///parser是XPathParser解析器对象,读取节点内数据,<configuration>是MyBatis配置文件中的顶层标签 // 解析 XML configuration 节点 parseConfiguration(parser.evalNode("/configuration")); return configuration; } /** * 解析 XML * * 具体 MyBatis 有哪些 XML 标签,参见 《XML 映射配置文件》http://www.mybatis.org/mybatis-3/zh/configuration.html * * @param root 根节点 */ private void parseConfiguration(XNode root) { try { //issue #117 read properties first // 解析 <properties /> 标签 propertiesElement(root.evalNode("properties")); // 解析 <settings /> 标签 Properties settings = settingsAsProperties(root.evalNode("settings")); // 加载自定义的 VFS 实现类 loadCustomVfs(settings); // 解析 <typeAliases /> 标签 typeAliasesElement(root.evalNode("typeAliases")); // 解析 <plugins /> 标签 pluginElement(root.evalNode("plugins")); // 解析 <objectFactory /> 标签 objectFactoryElement(root.evalNode("objectFactory")); // 解析 <objectWrapperFactory /> 标签 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); // 解析 <reflectorFactory /> 标签 reflectorFactoryElement(root.evalNode("reflectorFactory")); // 赋值 <settings /> 到 Configuration 属性 settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 // 解析 <environments /> 标签 environmentsElement(root.evalNode("environments")); // 解析 <databaseIdProvider /> 标签 databaseIdProviderElement(root.evalNode("databaseIdProvider")); // 解析 <typeHandlers /> 标签 typeHandlerElement(root.evalNode("typeHandlers")); // 解析 <mappers /> 标签 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
MappedStatement对象
与Mapper配置文件中的一个select/update/insert/delete节点相对应。
初始化Myabtis将MappedStatement对象封装在Configuration对象属性Map中
存储的key = 全限类名 + 方法名,通过key来找到对应的数据
/** * MappedStatement 映射 * * KEY:`${namespace}.${id}` */ protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");
Test方法步骤2中调用build()重载方法
/** * 创建 DefaultSqlSessionFactory 对象 * * @param config Configuration 对象 * @return DefaultSqlSessionFactory 对象 */ public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); //构建者设计模式 }
10.1.2 源码剖析执行SQL流程
SqlSession它有两个实现类:DefaultSqlSession (默认)和SqlSessionManager (弃用,不做介绍)
SqlSession是Myabatis中用于和数据库交互的顶层类,通常将它和ThreadLocal绑定,一个会话使用一个sqlSeesion,使用完毕后调用close()方法关闭资源
public class DefaultSqlSession implements SqlSession { private final Configuration configuration; //配置信息封装的对象 private final Executor executor; //执行器 }
10.1.3 源码剖析executor
10.1.4 源码剖析StatementHandler
10.2 Mapper代理方式
10.2.1 源码剖析getmapper()
10.2.2 源码剖析invoke()
10.3 二级缓冲源码剖析
10.3.1 启动二级缓冲
开启全局二级缓冲配置
<settings> <setting name="cacheEnabled" value="true"/> </settings>
在需要二级缓冲的Mapper中配置标签
<cache></cache>
在具体的CURD标签上配置useCache=true
<select id="findById" resultType="com.lagou.pojo.User" useCache="true"> select * from user where id = #{id} </select>
10.3.2 标签cache解析
(1)根据之前的mybatis源码剖析,xml的解析工作主要交给XMLConfigBuilder.parse()方法来实现
(2)先来看看是如何构建Cache对象的MapperBuilderAssistant.useNewCache()
我们看到一个Mapper.xml只会解析一次标签,也就是只创建一次Cache对象,放进configuration中,
(3)并将cache赋值给MapperBuilderAssistant.currentCache()
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
将Cache包装到MappedStatement
10.3.3 总结
在二级缓冲设计上,maybatis大量运用了装饰者模式,入CachingExecutor,以及各种Cache的装饰器。
二级缓冲实现了sqlsession之间的缓冲数据共享,属于namespace级别
二级缓冲具有丰富的缓冲策略
二级缓冲由多个装饰器,与基础缓冲组合而成
二级缓冲工作由一个缓冲装饰器CacheingExexutor和一个事务预警缓冲TransactionlCache完成
如果开启二级缓冲
查询方法先查看2级别缓冲中是否由数据,然后去以及缓冲中查找,都没有的话查询数据库
二级缓冲在调用tcm事务类中的commit方法才会生效,最初二级缓冲存放在一个map中,没有这个事务管理会产生桩读。
10.4 延迟加载源码剖析
10.4.1 什么是延迟加载
就是在需要用到的数据才进行加载,不需要用到数据的时候就不加载,延迟加载就是懒加载
注意:延迟加载是基于嵌套查询来实现的
一对多,多对多可以采用延迟加载
一对一(多对一)建议使用立即加载
Mybatis默认使用立即加载
10.4.2 局部延迟加载
<!--在association和collection标签中都有一个fetchType属性--> fetchType=“lazy” //延迟加载 //eager立即加载
10.4.3 全局延迟加载
<settings> <!--开启全局延迟加载功能--> <setting name="lazyLoadingEnabled" value="true"/> </settings>
10.4.4 延迟加载原理实现
它的原理是,使用 CGLIB 或 Javassist( 默认 ) 创建目标对象的代理对象。
当调用代理对象的延迟加载属性的 getting 方法时,进入拦截器方法invoke()。
总结:延迟加载主要是通过动态代理的形式实现,通过代理拦截到指定方法,执行数据加载。
10.4.5 延迟加载invoke()
判断是否延迟包含延迟加载属性
10.5 设计模式
10.5.1 Builder构建着模型
相对于工厂模型会产出一个完整的产品;Builder应用于更加复杂的对象构建,甚至只会构建产品的一部分
使用多个简单对象一步一步构建一个发杂的对象
创建一个简单对象
package com.topxin.constructor; public class Computer { private String disPlayer; private String mainUnit; private String mouse; private String keyboard; ...get/set/toString }
创建ComputerBuilder复杂类和getComputer()方法
package com.topxin.constructor; public class ComputerBuilder { private Computer computer = new Computer(); public void installDisPlayer(String disPlayer){ computer.setDisPlayer(disPlayer); } public void installMainUnit(String mainUnit){ computer.setMainUnit(mainUnit); } public void installKeyboard(String keyboard){ computer.setKeyboard(keyboard); } public void installMouse(String mouse){ computer.setMouse(mouse); } public Computer getComputer(){ return computer; } }
测试
package com.topxin.constructor; public class ConstructorTest { public static void main(String[]args){ ComputerBuilder computerBuilder = new ComputerBuilder(); computerBuilder.installDisPlayer("显万器"); computerBuilder.installMainUnit("主机"); computerBuilder.installKeyboard("键盘"); computerBuilder.installMouse("鼠标"); Computer computer = computerBuilder.getComputer(); System.out.println(computer); } }
10.5.2 工厂模式
简单工厂类:可以根据参数的不同返回不同类的实例
构架抽象类
package com.topxin.simpleFactory; public abstract class Computer { public abstract void start(); }
新建惠普类
package com.topxin.simpleFactory; public class HpComputer extends Computer { @Override public void start() { System.out.println("hp"); } }
理想类
package com.topxin.simpleFactory; public class LenovoComputer extends Computer { @Override public void start() { System.out.println("lenovo"); } }
工厂类
package com.topxin.simpleFactory; public class ComputerFactory { public static Computer createComputer(String type){ Computer computer = null; switch (type){ case "lenovo": computer = new LenovoComputer(); case "hp": computer = new HpComputer(); } return computer; } }
测试
通过工厂类createComputer可以创建不同类型的类
package com.topxin.simpleFactory; public class SimpleFactoryTest { public static void main(String[] args) { Computer hp = ComputerFactory.createComputer("hp"); hp.start(); } }
10.5.3 代理模式
Mybatis核心使用
接口
package com.topxin.dynamicprocy; public interface Person { void doSomething(); }
具体实现类
package com.topxin.dynamicprocy; public class Bob implements Person{ @Override public void doSomething() { System.out.println("Bob doSomeThing"); } }
根据JDC动态代理
package com.topxin.dynamicprocy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class JDKDynamicProxy implements InvocationHandler { //声明被代理的对象 private Person person; public JDKDynamicProxy(Person person) { this.person = person; } //获取代理对象 public Object getTarget(){ Object proxyInstance = Proxy.newProxyInstance(person.getClass().getClassLoader(), person.getClass().getInterfaces(), this); return proxyInstance; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("前置增强"); Object invoke = method.invoke(person, args); System.out.println("后置增强"); return invoke; } }
测试
获取目标最终都会调用invoke()方法
package com.topxin.dynamicprocy; public class ProxyTest { public static void main(String[] args) { Person person = new Bob(); person.doSomething(); Person proxy = (Person) new JDKDynamicProxy(new Bob()).getTarget(); proxy.doSomething(); } }
10.6 异常日志输出
使用ThreadLocal来管理ErrorContext
10.7 动态sql源码剖析
第十一部分 Mybatis-Plus
11.1 什么是Mybatis-Plus
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简
化开发、提高效率而生。
注意@TableName指定数据库表个实体类映射关系
11.2 mybatis+mp
11.3 mybatis+mp+spring
11.4 mybatis+mp+springboot
11.5
@TableName()
@TableName("数据库表名称") //指定表映射不同
@TableId()
@TableId(type = IdType.AUTO) //指定id类型为自增长等6中策略
@TableFiled()字段
@TableFiled(select = false) //不反悔,不查询改字段 @TableFiled(value = "数据库对应的字段") //字段映射名称不同 @TableFiled(exist = false) //数据库中不存在
分页拦截器
package com.topxin; import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @MapperScan("com.lagou.mp.mapper") //设置mapper接口的扫描包 public class MybatisPlusConfig { /*** 分页插件 */ @Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); } }
第十二部分 Mybatis简答题
12.1