mybatis(mybatis)

Mybatis 第部分:定义持久层框架 1.1 分析JDBC操作问题 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? characterEncoding=utf-8”, “root”, “root”); // 定义sql语句?表示占位符 String sql = “select * from user where username = ?”; // 获取预处理statement preparedStatement = connection.prepareStatement(sql); // 设置参数,第个参数为sql语句中参数的序号(从1开始),第个参数为设置的参数值 preparedStatement.setString(1, “tom”); // 向数据库发出sql执查询,查询出结果集 resultSet = preparedStatement.executeQuery(); // 遍历查询结果集 while (resultSet.next()) { int id = resultSet.getInt(“id”); String username = resultSet.getString(“username”); // 封装User user.setId(id); user.setUsername(username); } System.out.println(user); } } catch (Exception e) { e.printStackTrace(); } finally { // 释放资源 if (resultSet != null) { try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); JDBC问题总结: 原始jdbc开发存在的问题如下: 1、 数据库连接创建、释放频繁造成系统资源浪费,从影响系统性能。 2、 Sql语句在代码中硬编码,造成代码不易维护,实际应中sql变化的可能较,sql变动需要改变 java代码。 3、 使preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不定,可能 多也可能少,修改sql还要修改代码,系统不易维护。 4、 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据 库 记录封装成pojo对象解析较便 1.2 问题解决思路 ①使数据库连接池初始化连接资源 ②将sql语句抽取到xml配置件中 ③使反射、内省等底层技术,动将实体与表进属性与字段的动映射 1.3 定义框架设计 使端: 提供核配置件: sqlMapConfig.xml : 存放数据源信息,引mapper.xml Mapper.xml : sql语句的配置件信息 } } if (preparedStatement != null) { try { preparedStatement.close(); } catch (SQLException e) { e.printStackTrace(); } } if (connection != null) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } 框架端: 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.4 定义框架实现 在使端项中创建配置配置件 创建 sqlMapConfig.xml mapper.xml User实体 〈configuration〉 select * from user where id = #{id} and username =#{username}select * from userpublic class User { //主键标识 private Integer id; //户名 private String username; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } @Override public String toString() { return “User{” + “id=” + id + 再创建个Maven程并且导需要到的依赖坐标 “, username='” + username + ‘\” + ‘}’; } } UTF-8 UTF-8 1.8 1.8 1.8 mysql mysql-connector-java 5.1.17 c3p0 c3p0 0.9.1.2 log4j log4j 1.2.12 junit junit 4.10 dom4j dom4j 1.6.1 jaxen jaxen 1.1.6 Configuration MappedStatement public class Configuration { //数据源 private DataSource dataSource; //map集合: key:statementId value:MappedStatement private Map<string,mappedstatement> mappedStatementMap = new HashMap<string, mappedstatement=””>(); public DataSource getDataSource() { return dataSource; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } public Map<string, mappedstatement=””> getMappedStatementMap() { return mappedStatementMap; } public void setMappedStatementMap(Map<string, mappedstatement=””> mappedStatementMap) { this.mappedStatementMap = mappedStatementMap; } } public class MappedStatement { //id private Integer id; //sql语句 private String sql; //输参数 private Class paramterType; //输出参数 private Class resultType; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getSql() { return sql; } public void setSql(String sql) { this.sql = sql; Resources SqlSessionFactoryBuilder XMLConfigerBuilder } 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; } } public class Resources { public static InputStream getResourceAsSteam(String path){ InputStream resourceAsStream = Resources.class.getClassLoader.getResourceAsStream(path); return resourceAsStream; } } public class SqlSessionFactoryBuilder { private Configuration configuration; public SqlSessionFactoryBuilder() { this.configuration = new Configuration(); } public SqlSessionFactory build(InputStream inputStream) throws DocumentException, PropertyVetoException, ClassNotFoundException { //1.解析配置件,封装Configuration XMLConfigerBuilder xmlConfigerBuilder = new XMLConfigerBuilder(configuration); Configuration configuration = xmlConfigerBuilder.parseConfiguration(inputStream); //2.创建 sqlSessionFactory SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration); return sqlSessionFactory; } public class XMLConfigerBuilder { XMLMapperBuilder private Configuration configuration; public XMLConfigerBuilder(Configuration configuration) { this.configuration = new Configuration(); } public Configuration parseConfiguration(InputStream inputStream) throws DocumentException, PropertyVetoException, ClassNotFoundException { Document document = new SAXReader().read(inputStream); // Element rootElement = document.getRootElement(); List propertyElements = rootElement.selectNodes(“//property”); Properties properties = new Properties(); for (Element propertyElement : propertyElements) { String name = propertyElement.attributeValue(“name”); String value = propertyElement.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 configuration.setDataSource(comboPooledDataSource); //mapper 部分 List mapperElements = rootElement.selectNodes(“//mapper”); XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration); for (Element mapperElement : mapperElements) { String mapperPath = mapperElement.attributeValue(“resource”); InputStream resourceAsSteam = Resources.getResourceAsSteam(mapperPath); xmlMapperBuilder.parse(resourceAsSteam); } return configuration; } public class XMLMapperBuilder { private Configuration configuration; public XMLMapperBuilder(Configuration configuration) { sqlSessionFactory 接及D efaultSqlSessionFactory 实现类 this.configuration = configuration; } public void parse(InputStream inputStream) throws DocumentException, ClassNotFoundException { Document document = new SAXReader().read(inputStream); Element rootElement = document.getRootElement(); String namespace = rootElement.attributeValue(“namespace”); List select = rootElement.selectNodes(“select”); for (Element element : select) { //id的值 String id = element.attributeValue(“id”); String paramterType = element.attributeValue(“paramterType”); String resultType = element.attributeValue(“resultType”); //输参 数class Class paramterTypeClass = getClassType(paramterType); //返回结果class Class resultTypeClass = getClassType(resultType); //statementId String key = namespace + “.” + id; //sql语句 String textTrim = element.getTextTrim(); //封装 mappedStatement MappedStatement mappedStatement = new MappedStatement(); mappedStatement.setId(id); mappedStatement.setParamterType(paramterTypeClass); mappedStatement.setResultType(resultTypeClass); mappedStatement.setSql(textTrim); //填充 configuration configuration.getMappedStatementMap().put(key, mappedStatement); private Class getClassType (String paramterType) throws ClassNotFoundException { Class aClass = Class.forName(paramterType); return aClass; } } sqlSession 接及 DefaultSqlSession 实现类 public interface SqlSessionFactory { public SqlSession openSession(); } public class DefaultSqlSessionFactory implements SqlSessionFactory { private Configuration configuration; public DefaultSqlSessionFactory(Configuration configuration) { this.configuration = configuration; } public SqlSession openSession(){ return new DefaultSqlSession(configuration); } } public interface SqlSession { public List selectList(String statementId, Object… param) Exception; public T selectOne(String statementId,Object… params) throws Exception; public void close() throws SQLException; } public class DefaultSqlSession implements SqlSession { private Configuration configuration; public DefaultSqlSession(Configuration configuration) { this.configuration = configuration; //处理器对象 private Executor simpleExcutor = new SimpleExecutor(); public List < E > selectList(String statementId, Object…param) throws Exception { MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId); List query = simpleExcutor.query(configuration, mappedStatement, param); return query; } //selectOne 中调 selectList public T selectOne(String statementId, Object…params) throws Exception { List objects = selectList(statementId, params); if (objects.size() == 1) { return (T) objects.get(0); } else { throw new RuntimeException(“返回结果过多”); } Executor SimpleExecutor } public void close () throws SQLException { simpleExcutor.close(); } } public interface Executor { List query(Configuration configuration, MappedStatement mappedStatement,Object[] param) throws Exception; void close() throws SQLException; } public class SimpleExecutor implements Executor { private Connection connection = null; public List query(Configuration configuration, MappedStatement mappedStatement, Object[] param) throws SQLException, NoSuchFieldException, IllegalAccessException, InstantiationException, IntrospectionException, InvocationTargetException { //获取连接 connection = configuration.getDataSource().getConnection(); // select * from user where id = #{id} and username = #{username} String sql = mappedStatement.getSql(); //对sql进处理 BoundSql boundsql = getBoundSql(sql); // select * from where id = ? and username = ? String finalSql = boundsql.getSqlText(); //获取传参数类型 Class paramterType = mappedStatement.getParamterType(); //获取预编译preparedStatement对象 PreparedStatement preparedStatement = connection.prepareStatement(finalSql); List parameterMappingList = boundsql.getParameterMappingList(); for (int i = 0; i < parameterMappingList.size(); i++) { ParameterMapping parameterMapping = parameterMappingList.get(i); String name = parameterMapping.getName(); //反射 Field declaredField = paramterType.getDeclaredField(name); declaredField.setAccessible(true); //参数的值 Object o = declaredField.get(param[0]); //给占位符赋值 preparedStatement.setObject(i + 1, o); } ResultSet resultSet = preparedStatement.executeQuery(); Class resultType = mappedStatement.getResultType(); ArrayList results = new ArrayList(); while (resultSet.next()) { ResultSetMetaData metaData = resultSet.getMetaData(); (E) resultType.newInstance(); int columnCount = metaData.getColumnCount(); for (int i = 1; i <= columnCount; i++) { //属性名 String columnName = metaData.getColumnName(i); //属性值 Object value = resultSet.getObject(columnName); //创建属性描述器,为属性成读写法 PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultType); //获取写法 Method writeMethod = propertyDescriptor.getWriteMethod(); //向类中写值 writeMethod.invoke(o, value); } results.add(o); } return results; } @Override public void close() throws SQLException { connection.close(); } private BoundSql getBoundSql(String sql) { //标记处理类:主要是配合通标记解析器GenericTokenParser类完成对配置件等的解 析作,其中TokenHandler主要完成处理 ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler(); //GenericTokenParser :通的标记解析器,完成了代码段中的占位符的解析,然后再根 据给定的标记处理器(TokenHandler)来进表达式的处理 //三个参数:分别为openToken (开始标记)、closeToken (结束标记)、handler (标记 处 理器) GenericTokenParser genericTokenParser = new GenericTokenParser(“# {“, “}”, parameterMappingTokenHandler); String parse = genericTokenParser.parse(sql); List parameterMappings = parameterMappingTokenHandler.getParameterMappings(); BoundSql boundSql = new BoundSql(parse, parameterMappings); return boundSql; BoundSql 1.5 定义框架优化 通过上述我们的定义框架,我们解决了JDBC操作数据库带来的些问题:例如频繁创建释放数据库连 接,硬编码,动封装返回结果集等问题,但是现在我们继续来分析刚刚完成的定义框架代码,有没 有什么问题? 问题如下: dao的实现类中存在重复的代码,整个操作的过程模板重复(创建sqlsession,调sqlsession 法,关闭 sqlsession) dao的实现类中存在硬编码,调sqlsession的法时,参数statement的id硬编码 } } public class BoundSql { //解析过后的sql语句 private String sqlText; //解析出来的参数 private List parameterMappingList = new ArrayList(); public BoundSql(String sqlText, List parameterMappingList) { this.sqlText = sqlText; this.parameterMappingList = parameterMappingList; } public String getSqlText() { return sqlText; } public void setSqlText(String sqlText) { this.sqlText = sqlText; } public List getParameterMappingList() { return parameterMappingList; } public void setParameterMappingList(List parameterMappingList) { this.parameterMappingList = parameterMappingList; } } 解决:使代理模式来创建接的代理对象 在sqlSession中添加法 实现类 @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); Systemout.println(userl); } public interface SqlSession { public T getMappper(Class mapperClass); @Override public 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); } }); 第部分:Mybatis相关概念 2.1 对象/关系数据库映射(ORM) ORM全称Object/Relation Mapping:表示对象-关系映射的缩写 ORM完成向对象的编程语到关系数据库的映射。当ORM框架完成映射后,程序员既可以利向 对象程序设计语的简单易性,可以利关系数据库的技术优势。ORM把关系数据库包装成向对 象的模型。ORM框架是向对象设计语与关系数据库发展不同步时的中间解决案。采ORM框架 后,应程序不再直接访问底层数据库,是以向对象的式来操作持久化对象,ORM框架则将这 些向对象的操作转换成底层SQL操作。ORM框架实现的效果:把对持久化对象的保存、修改、删除 等操作,转换为对数据库的操作 2.2 Mybatis简介 MyBatis是款优秀的基于ORM的半动轻量级持久层框架,它持定制化SQL、存储过程以及级映 射。MyBatis避免了乎所有的JDBC代码和动设置参数以及获取结果集。MyBatis可以使简单的 XML或注解来配置和映射原类型、接和Java的POJO (Plain Old Java Objects,普通式Java对 象) 为数据库中的记录。 2.3 Mybatis历史 原是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 Mybatis优势 Mybatis是个半动化的持久层框架,对开发员开说,核sql还是需要进优化,sql和java编 码进分离,功能边界清晰,个专注业务,个专注数据。 分析图示如下: return o; } 第三部分:Mybatis基本应 3.1 快速 MyBatis官地址:http://www.mybatis.org/mybatis-3/ 3.1.1 开发步骤: ①添加MyBatis的坐标 ②创建user数据表 ③编写User实体类 ④编写映射件UserMapper.xml ⑤编写核件SqlMapConfig.xml ⑥编写测试类 3.1.1 环境搭建: 1)导MyBatis的坐标和其他相关坐标 2) 创建user数据表 3) 编写User实体 UTF-8 UTF-8 1.8 1.8 1.8 org.mybatis mybatis 3.4.5 mysql mysql-connector-java 5.1.6 runtime junit junit 4.12 test log4j log4j 1.2.12 4)编写UserMapper映射件 5) 编写MyBatis核件 6) 编写测试代码 public class User { private int id; private String username; private String password; //省略get个set法 } select * from User3.1.4 MyBatis的增删改查操作 MyBatis的插数据操作 1)编写UserMapper映射件 2)编写插实体User的代码 3)插操作注意问题 插语句使insert标签 在映射件中使parameterType属性指定要插的数据类型 Sql语句中使#{实体属性名}式引实体中的属性值 //加载核配置件 InputStream resourceAsStream = Resources.getResourceAsStream(“SqlMapConfig.xml”); //获得sqlSession对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //获得sqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); //执sql语句 List userList = sqlSession.selectList(“userMapper.findAll”); //打印结果 System.out.println(userList); //释放资源 sqlSession.close(); insert into user values(#{id},#{username},#{password}) InputStream resourceAsStream = Resources.getResourceAsStream(“SqlMapConfig.xml”); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); int insert = sqlSession.insert(“userMapper.add”, user); System.out.println(insert); //提交事务 sqlSession.commit(); sqlSession.close(); 插操作使的API是sqlSession.insert(“命名空间.id”,实体对象); 插操作涉及数据库数据变化,所以要使sqlSession对象显示的提交事务,即sqlSession.commit() 3.1.5 MyBatis的修改数据操作 1)编写UserMapper映射件 2)编写修改实体User的代码 3)修改操作注意问题 修改语句使update标签 修改操作使的API是sqlSession.update(“命名空间.id”,实体对象); 3.1.6 MyBatis的删除数据操作 1)编写UserMapper映射件 2)编写删除数据的代码 update user set username=#{username},password=#{password} where id=# {id} InputStream resourceAsStream = Resources.getResourceAsStream(“SqlMapConfig.xml”); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); int update = sqlSession.update(“userMapper.update”, user); System.out.println(update); sqlSession.commit(); sqlSession.close(); delete from user where id=#{id} 3)删除操作注意问题 删除语句使delete标签 Sql语句中使#{任意字符串}式引传递的单个参数 删除操作使的API是sqlSession.delete(“命名空间.id”,Object); 3.1.5 MyBatis的映射件概述 3.1.6 核配置件分析: MyBatis核配置件层级关系 InputStream resourceAsStream = Resources.getResourceAsStream(“SqlMapConfig.xml”); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); int delete = sqlSession.delete(“userMapper.delete”,3); System.out.println(delete); sqlSession.commit(); sqlSession.close(); MyBatis常配置解析 1)environments标签 数据库环境的配置,持多环境配置 其中,事务管理器(transactionManager)类型有两种: JDBC:这个配置就是直接使了JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作 域。 MANAGED:这个配置乎没做什么。它从来不提交或回滚个连接,是让容器来管理事务的整个 命周期(如 JEE 应服务器的上下)。 默认情况下它会关闭连接,然些容器并不希望这样,因 此需要将 closeConnection 属性设置为 false 来阻它默认的关闭为。 其中,数据源(dataSource)类型有三种: UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。 POOLED:这种数据源的实现利“池”的概念将 JDBC 连接对象组织起来。 JNDI:这个数据源的实现是为了能在如 EJB 或应服务器这类容器中使,容器可以集中或在外部配 置数据源,然后放置个 JNDI 上下的引。 2)mapper标签 该标签的作是加载映射的,加载式有如下种: 3.1.7 Mybatis相应API介绍 SqlSession构建器SqlSessionFactoryBuilder 常API:SqlSessionFactory build(InputStream inputStream) 通过加载mybatis的核件的输流的形式构建个SqlSessionFactory对象 其中, Resources 具类,这个类在 org.apache.ibatis.io 包中。Resources 类帮助你从类路径下、 件系统或个 web URL 中加载资源件。 SqlSession对象SqlSessionFactory SqlSessionFactory 有多个个法创建SqlSession 实例。常的有如下两个: 使相对于类路径的资源引,例如: 使完全限定资源定位符(URL),例如: 使映射器接实现类的完全限定类名,例如: 将包内的映射器接实现全部注册为映射器,例如: String resource = “org/mybatis/builder/mybatis-config.xml”; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(inputStream); SqlSession会话对象 SqlSession 实例在 MyBatis 中是常强的个类。在这你会看到所有执语句、提交或回滚事务 和获取映射器实例的法。 执语句的法主要有: 操作事务的法主要有: 3.2 Mybatis的Dao层实现 3.2.1 传统开发式 编写UserDao接 编写UserDaoImpl实现 T selectOne(String statement, Object parameter) List 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() public interface UserDao { List findAll() throws IOException; } 测试传统式 3.2.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的类型相同 编写UserMapper接 public class UserDaoImpl implements UserDao { public List findAll() throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream(“SqlMapConfig.xml”); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); List userList = sqlSession.selectList(“userMapper.findAll”); sqlSession.close(); return userList; } } @Test public void testTraditionDao() throws IOException { UserDao userDao = new UserDaoImpl(); List all = userDao.findAll(); System.out.println(all); } 测试代理式 第四部分:Mybatis配置件深 4.1 核配置件SqlMapConfig.xml 4.1.1 MyBatis核配置件层级关系 @Test public void testProxyDao() throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream(“SqlMapConfig.xml”); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); //获得MyBatis框架成的UserMapper接的实现类 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = userMapper.findById(1); System.out.println(user); sqlSession.close(); } 4.2 MyBatis常配置解析 1)environments标签 数据库环境的配置,持多环境配置 其中,事务管理器(transactionManager)类型有两种: JDBC:这个配置就是直接使了JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作 域。 MANAGED:这个配置乎没做什么。它从来不提交或回滚个连接,是让容器来管理事务的整个 命周期(如 JEE 应服务器的上下)。 默认情况下它会关闭连接,然些容器并不希望这样,因 此需要将 closeConnection 属性设置为 false 来阻它默认的关闭为。 其中,数据源(dataSource)类型有三种: UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。 POOLED:这种数据源的实现利“池”的概念将 JDBC 连接对象组织起来。 JNDI:这个数据源的实现是为了能在如 EJB 或应服务器这类容器中使,容器可以集中或在外部配 置数据源,然后放置个 JNDI 上下的引。 2)mapper标签 该标签的作是加载映射的,加载式有如下种: 3)Properties标签 实际开发中,习惯将数据源的配置信息单独抽取成个properties件,该标签可以加载额外配置的 properties件 4)typeAliases标签 类型别名是为Java 类型设置个短的名字。原来的类型名称配置如下 使相对于类路径的资源引,例如: 使完全限定资源定位符(URL),例如: 使映射器接实现类的完全限定类名,例如: 将包内的映射器接实现全部注册为映射器,例如: 配置typeAliases,为com.lagou.domain.User定义别名为user 上我们是定义的别名,mybatis框架已经为我们设置好的些常的类型的别名 4.2 映射配置件mapper.xml 动态sql语句 动态sql语句概述 Mybatis 的映射件中,前我们的 SQL 都是较简单的,有些时候业务逻辑复杂时,我们的 SQL是 动态变化的,此时在前的学习中我们的 SQL 就不能满要求了。 参考的官档,描述如下: 动态 SQL 之 我们根据实体类的不同取值,使不同的 SQL语句来进查询。如在 id如果不为空时可以根据id查 询,如果username 不同空时还要加户名作为条件。这种情况在我们的多条件组合查询中经常会碰 到。 当查询条件id和username都存在时,控制台打印的sql语句如下: 当查询条件只有id存在时,控制台打印的sql语句如下:select * from User and id=#{id} and username=#{username}… … … //获得MyBatis框架成的UserMapper接的实现类 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User condition = new User(); condition.setId(1); condition.setUsername(“lucy”); User user = userMapper.findByCondition(condition); … … … … … … //获得MyBatis框架成的UserMapper接的实现类 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User condition = new User(); condition.setId(1); User user = userMapper.findByCondition(condition); … … … 动态 SQL 之 循环执sql的拼接操作,例如:SELECT * FROM USER WHERE id IN (1,2,5)。 测试代码段如下: foreach标签的属性含义如下: 标签于遍历集合,它的属性: collection:代表要遍历的集合元素,注意编写时不要写#{} open:代表语句的开始部分 close:代表结束部分 item:代表遍历集合的每个元素,成的变量名 sperator:代表分隔符select * from User #{id}… … … //获得MyBatis框架成的UserMapper接的实现类 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); int[] ids = new int[]{2,5}; List userList = userMapper.findByIds(ids); System.out.println(userList); … … … SQL段抽取 Sql 中可将重复的 sql 提取出来,使时 include 引即可,最终达到 sql 重的的 第五部分:Mybatis复杂映射开发 5.1 对查询 5.1.1 对查询的模型 户表和订单表的关系为,个户有多个订单,个订单只从属于个户 对查询的需求:查询个订单,与此同时查询出该订单所属的户 5.1.2对查询的语句 对应的sql语句:select * from orders o,user u where o.uid=u.id; 查询的结果如下: where id=#{id}#{id}5.1.3 创建Order和User实体 5.1.4 创建OrderMapper接 5.1.5 配置OrderMapper.xml public class Order { private int id; private Date ordertime; private double total; //代表当前订单从属于哪个客户 private User user; } public class User { private int id; private String username; private String password; private Date birthday; } public interface OrderMapper { List findAll(); }select * from orders o,user u where o.uid=u.id其中还可以配置如下: 5.1.6 测试结果 5.2 对多查询 5.2.1 对多查询的模型 户表和订单表的关系为,个户有多个订单,个订单只从属于个户 对多查询的需求:查询个户,与此同时查询出该户具有的订单 5.2.2 对多查询的语句 对应的sql语句:select *,o.id oid from user u left join orders o on u.id=o.uid; 查询的结果如下: OrderMapper mapper = sqlSession.getMapper(OrderMapper.class); List all = mapper.findAll(); for(Order order : all){ System.out.println(order); } 5.2.3 修改User实体 5.2.4 创建UserMapper接 5.2.5 配置UserMapper.xml public class Order { private int id; private Date ordertime; private double total; //代表当前订单从属于哪个客户 private User user; } public class User { private int id; private String username; private String password; private Date birthday; //代表当前户具备哪些订单 private List orderList; } public interface UserMapper { List findAll(); } 5.2.6 测试结果 5.3 多对多查询 5.3.1 多对多查询的模型 户表和表的关系为,个户有多个,个被多个户使 多对多查询的需求:查询户同时查询出该户的所有select *,o.id oid from user u left join orders o on u.id=o.uidUserMapper mapper = sqlSession.getMapper(UserMapper.class); List all = mapper.findAll(); for(User user : all){ System.out.println(user.getUsername()); List orderList = user.getOrderList(); for(Order order : orderList){ System.out.println(order); } System.out.println(“———————————-“); } 5.3.2 多对多查询的语句 对应的sql语句:select u.,r.,r.id rid from user u left join user_role ur on u.id=ur.user_id inner join role r on ur.role_id=r.id; 查询的结果如下: 5.3.3 创建Role实体,修改User实体 5.3.4 添加UserMapper接法 5.3.5 配置UserMapper.xml public class User { private int id; private String username; private String password; private Date birthday; //代表当前户具备哪些订单 private List orderList; //代表当前户具备哪些 private List roleList; } public class Role { private int id; private String rolename; } List findAllUserAndRole(); 5.3.6 测试结果 5.4 知识结 MyBatis多表配置式: 对配置:使做配置 对多配置:使+做配置 多对多配置:使+做配置 第六部分:Mybatis注解开发 6.1 MyBatis的常注解select u.*,r.*,r.id rid from user u left join user_role ur on u.id=ur.user_id inner join role r on ur.role_id=r.idUserMapper mapper = sqlSession.getMapper(UserMapper.class); List all = mapper.findAllUserAndRole(); for(User user : all){ System.out.println(user.getUsername()); List roleList = user.getRoleList(); for(Role role : roleList){ System.out.println(role); } System.out.println(“———————————-“); } 这年来注解开发越来越流,Mybatis也可以使注解开发式,这样我们就可以减少编写Mapper 映射件了。我们先围绕些基本的CRUD来学习,再学习复杂映射多表操作。 @Insert:实现新增 @Update:实现更新 @Delete:实现删除 @Select:实现查询 @Result:实现结果集封装 @Results:可以与@Result 起使,封装多个结果集 @One:实现对结果集封装 @Many:实现对多结果集封装 6.2 MyBatis的增删改查 我们完成简单的user表的增删改查的操作 private UserMapper userMapper; @Before public void before() throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream(“SqlMapConfig.xml”); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(true); userMapper = sqlSession.getMapper(UserMapper.class); } @Test public void testAdd() { User user = new User(); user.setUsername(“测试数据”); user.setPassword(“123”); user.setBirthday(new Date()); userMapper.add(user); } @Test public void testUpdate() throws IOException { User user = new User(); user.setId(16); user.setUsername(“测试数据修改”); user.setPassword(“abc”); user.setBirthday(new Date()); userMapper.update(user); } 修改MyBatis的核配置件,我们使了注解替代的映射件,所以我们只需要加载使了注解的 Mapper接即可 或者指定扫描包含映射关系的接所在的包也可以 6.3 MyBatis的注解实现复杂映射开发 实现复杂关系映射之前我们可以在映射件中通过配置来实现,使注解开发后,我们可以使 @Results注解,@Result注解,@One注解,@Many注解组合完成复杂关系的配置 @Test public void testDelete() throws IOException { userMapper.delete(16); } @Test public void testFindById() throws IOException { User user = userMapper.findById(1); System.out.println(user); } @Test public void testFindAll() throws IOException { List all = userMapper.findAll(); for(User user : all){ System.out.println(user); } } 6.4 对查询 6.4.1 对查询的模型 户表和订单表的关系为,个户有多个订单,个订单只从属于个户 对查询的需求:查询个订单,与此同时查询出该订单所属的户 6.4.2 对查询的语句 对应的sql语句: 查询的结果如下: select * from orders; select * from user where id=查询出订单的uid; 6.4.3 创建Order和User实体 6.4.4 创建OrderMapper接 6.4.5 使注解配置Mapper public class Order { private int id; private Date ordertime; private double total; //代表当前订单从属于哪个客户 private User user; } public class User { private int id; private String username; private String password; private Date birthday; } public interface OrderMapper { List findAll(); } 6.4.6 测试结果 6.5 对多查询 6.5.1 对多查询的模型 户表和订单表的关系为,个户有多个订单,个订单只从属于个户 对多查询的需求:查询个户,与此同时查询出该户具有的订单 public interface OrderMapper { @Select(“select * from orders”) @Results({ @Result(id=true,property = “id”,column = “id”), @Result(property = “ordertime”,column = “ordertime”), @Result(property = “total”,column = “total”), @Result(property = “user”,column = “uid”, javaType = User.class, one = @One(select = “com.lagou.mapper.UserMapper.findById”)) }) List findAll(); } public interface UserMapper { @Select(“select * from user where id=#{id}”) User findById(int id); } @Test public void testSelectOrderAndUser() { List all = orderMapper.findAll(); for(Order order : all){ System.out.println(order); } } 6.5.2 对多查询的语句 对应的sql语句: 查询的结果如下: 6.5.3 修改User实体 6.5.4 创建UserMapper接 select * from user; select * from orders where uid=查询出户的id; public class Order { private int id; private Date ordertime; private double total; //代表当前订单从属于哪个客户 private User user; } public class User { private int id; private String username; private String password; private Date birthday; //代表当前户具备哪些订单 private List orderList; } 6.5.5 使注解配置Mapper 6.5.6 测试结果 6.6 多对多查询 List findAllUserAndOrder(); public interface UserMapper { @Select(“select * from user”) @Results({ @Result(id = true,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.lagou.mapper.OrderMapper.findByUid”)) }) List findAllUserAndOrder(); } public interface OrderMapper { @Select(“select * from orders where uid=#{uid}”) List findByUid(int uid); } List all = userMapper.findAllUserAndOrder(); for(User user : all){ System.out.println(user.getUsername()); List orderList = user.getOrderList(); for(Order order : orderList){ System.out.println(order); } System.out.println(“—————————–“); } 6.6.1 多对多查询的模型 户表和表的关系为,个户有多个,个被多个户使 多对多查询的需求:查询户同时查询出该户的所有 6.6.2 多对多查询的语句 对应的sql语句: 查询的结果如下: 6.6.3 创建Role实体,修改User实体 select * from user; select * from role r,user_role ur where r.id=ur.role_id and ur.user_id=户的id public class User { private int id; private String username; private String password; private Date birthday; //代表当前户具备哪些订单 private List orderList; //代表当前户具备哪些 private List roleList; } public class Role { private int id; private String rolename; 6.6.4 添加UserMapper接法 6.6.5 使注解配置Mapper 6.6.6 测试结果 } List findAllUserAndRole(); public interface UserMapper { @Select(“select * from user”) @Results({ @Result(id = true,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.lagou.mapper.RoleMapper.findByUid”)) }) List findAllUserAndRole();} public interface RoleMapper { @Select(“select * from role r,user_role ur where r.id=ur.role_id and ur.user_id=#{uid}”) List findByUid(int uid); } UserMapper mapper = sqlSession.getMapper(UserMapper.class); List all = mapper.findAllUserAndRole(); for(User user : all){ System.out.println(user.getUsername()); List roleList = user.getRoleList(); for(Role role : roleList){ System.out.println(role); } System.out.println(“———————————-“); } 第七部分:Mybatis缓存 7.1 级缓存 ①、在个sqlSession中,对User表根据id进两次查询,查看他们发出sql语句的情况 查看控制台打印情况: @Test public void test1(){ //根据 sqlSessionFactory 产 session SqlSession sqlSession = sessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //第次查询,发出sql语句,并将查询出来的结果放进缓存中 User u1 = userMapper.selectUserByUserId(1); System.out.println(u1); //第次查询,由于是同个sqlSession,会在缓存中查询结果 //如果有,则直接从缓存中取出来,不和数据库进交互 User u2 = userMapper.selectUserByUserId(1); System.out.println(u2); sqlSession.close(); } ② 、同样是对user表进两次查询,只不过两次查询之间进了次update操作。 查看控制台打印情况: ③、总结 1、第次发起查询户id为1的户信息,先去找缓存中是否有id为1的户信息,如果没有,从 数据 库查询户信息。得到户信息,将户信息存储到级缓存中。 2、 如果中间sqlSession去执commit操作(执插、更新、删除),则会清空SqlSession中的 级缓存,这样做的的为了让缓存中存储的是最新的信息,避免脏读。 3、 第次发起查询户id为1的户信息,先去找缓存中是否有id为1的户信息,缓存中有,直 接从 缓存中获取户信息 @Test public void test2(){ //根据 sqlSessionFactory 产 session SqlSession sqlSession = sessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //第次查询,发出sql语句,并将查询的结果放缓存中 User u1 = userMapper.selectUserByUserId( 1 ); System.out.println(u1); //第步进了次更新操作,sqlSession.commit() u1.setSex(“”); userMapper.updateUserByUserId(u1); sqlSession.commit(); //第次查询,由于是同个sqlSession.commit(),会清空缓存信息 //则此次查询也会发出sql语句 User u2 = userMapper.selectUserByUserId(1); System.out.println(u2); 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(); 创建缓存key会经过系列的update法,udate法由个CacheKey这个对象来执的,这个 update法最终由updateList的list来把五个值存进去,对照上的代码和下的图示,你应该能 理解 这五个值都是什么了 这需要注意下最后个值,configuration.getEnvironment().getId()这是什么,这其实就是 定义在 mybatis-config.xml中的标签,如下。 //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()); } 那么我们回归正题,那么创建完缓存之后该在何处呢?总不会凭空创建个缓存不使吧?绝对不会 的,经过我们对级缓存的探究之后,我们发现级缓存更多是于查询操作,毕竟级缓存也叫做查 询缓存吧,为什么叫查询缓存我们会说。我们先来看下这个缓存到底在哪了,我们跟踪到 query法如下: Override public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameter); //创建缓存 CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); } @SuppressWarnings(“unchecked”) Override public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { … list = resultHandler == null ? (List) localCache.getObject(key) : null; if (list != null) { //这个主要是处理存储过程的。 handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } … } // queryFromDatabase 法 private List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List list; 如果查不到的话,就从数据库查,在queryFromDatabase中,会对localcache进写。 localcache 对象的put法最终交给Map进存放 7.2 级缓存 级缓存的原理和级缓存原理样,第次查询,会将数据放缓存中,然后第次查询则会直接去 缓存中取。但是级缓存是基于sqlSession的,级缓存是基于mapper件的namespace的,也 就 是说多个sqlSession可以共享个mapper中的级缓存区域,并且如果两个mapper的namespace 相 同,即使是两个mapper,那么这两个mapper中执sql查询到的数据也将存在相同的级缓存区域 中 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; } private Map<object, object=””> cache = new HashMap<object, object=””>(); @Override public void putObject(Object key, Object value) { cache.put(key, value); } 如何使级缓存 ① 、开启级缓存 和级缓存默认开启不样,级缓存需要我们动开启 先在全局配置件sqlMapConfig.xml件中加如下代码: 其次在UserMapper.xml件中开启缓存 我们可以看到mapper.xml件中就这么个空标签,其实这可以配置,PerpetualCache这个类是 mybatis默认实现缓存功能的类。我们不写type就使mybatis默认的缓存,也可以去实现Cache接 来定义缓存。 我们可以看到级缓存底层还是HashMap结构 开启了级缓存后,还需要将要缓存的pojo实现Serializable接,为了将缓存数据取出执反序列化操 作,因为级缓存数据存储介质多种多样,不定只存在内存中,有可能存在硬盘中,如果我们要再取 这个缓存的话,就需要反序列化了。所以mybatis中的pojo都去实现Serializable接 ③、测试 、测试级缓存和sqlSession关 public class PerpetualCache implements Cache { private final String id; private MapcObject, Object> cache = new HashMapC); public PerpetualCache(St ring id) { this.id = id; } public class User implements Serializable( //户ID private int id; //户姓名 private String username; //户性别 private String sex; } @Test 可以看出上两个不同的sqlSession,第个关闭了,第次查询依然不发出sql查询语句 、测试执commit()操作,级缓存数据清空 查看控制台情况: public void testTwoCache(){ //根据 sqlSessionFactory 产 session SqlSession sqlSession1 = sessionFactory.openSession(); SqlSession sqlSession2 = sessionFactory.openSession(); UserMapper userMapper1 = sqlSession1.getMapper(UserMapper. class ); UserMapper userMapper2 = sqlSession2.getMapper(UserMapper. class ); //第次查询,发出sql语句,并将查询的结果放缓存中 User u1 = userMapper1.selectUserByUserId(1); System.out.println(u1); sqlSession1.close(); //第次查询完后关闭 sqlSession //第次查询,即使sqlSession1已经关闭了,这次查询依然不发出sql语句 User u2 = userMapper2.selectUserByUserId(1); System.out.println(u2); sqlSession2.close(); @Test public void testTwoCache(){ //根据 sqlSessionFactory 产 session SqlSession sqlSession1 = sessionFactory.openSession(); SqlSession sqlSession2 = sessionFactory.openSession(); SqlSession sqlSession3 = sessionFactory.openSession(); String statement = “com.lagou.pojo.UserMapper.selectUserByUserld” ; UserMapper userMapper1 = sqlSession1.getMapper(UserMapper. class ); UserMapper userMapper2 = sqlSession2.getMapper(UserMapper. class ); UserMapper userMapper3 = sqlSession2.getMapper(UserMapper. class ); //第次查询,发出sql语句,并将查询的结果放缓存中 User u1 = userMapperl.selectUserByUserId( 1 ); System.out.println(u1); sqlSessionl .close(); //第次查询完后关闭sqlSession //执更新操作,commit() u1.setUsername( “aaa” ); userMapper3.updateUserByUserId(u1); sqlSession3.commit(); //第次查询,由于上次更新操作,缓存数据已经清空(防数据脏读),这必须再次发出sql语 User u2 = userMapper2.selectUserByUserId( 1 ); System.out.println(u2); sqlSession2.close(); } ④、useCache和flushCache mybatis中还可以配置userCache和flushCache等配置项,userCache是来设置是否禁级缓 存 的,在statement中设置useCache=false可以禁当前select语句的级缓存,即每次查询都会发出 sql 去查询,默认情况是true,即该sql使级缓存 这种情况是针对每次查询都需要最新的数据sql,要设置成useCache=false,禁级缓存,直接从数 据 库中获取。 在mapper的同个namespace中,如果有其它insert、update, delete操作数据后需要刷新缓 存,如 果不执刷新缓存会出现脏读。 设置statement配置中的flushCache=”true”属性,默认情况下为true,即刷新缓存,如果改成false则 不 会刷新。使缓存时如果动修改数据库表中的查询数据会出现脏读。 般下执完commit操作都需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏 读。所以我们不设置,默认即可 7.3 级缓存整合redisselect * from user where id=#{id}select * from user where id=#{id}上我们介绍了 mybatis带的级缓存,但是这个缓存是单服务器作,法实现分布式缓存。 那么 什么是分布式缓存呢?假设现在有两个服务器1和2,户访问的时候访问了 1服务器,查询后的缓 存就 会放在1服务器上,假设现在有个户访问的是2服务器,那么他在2服务器上就法获取刚刚那个 缓 存,如下图所示: 为了解决这个问题,就得找个分布式的缓存,专来存储缓存数据的,这样不同的服务器要缓存数 据都往它那存,取缓存数据也从它那取,如下图所示: 如上图所示,在个不同的服务器之间,我们使第三缓存框架,将缓存都放在这个第三框架中, 然后论有多少台服务器,我们都能从缓存中获取数据。 这我们介绍mybatis与redis的整合。 刚刚提到过,mybatis提供了个eache接,如果要实现的缓存逻辑,实现cache接开发即可。 mybati s本身默认实现了个,但是这个缓存的实现法实现分布式缓存,所以我们要来实现。 redis分布式缓存就可以,mybatis提供了个针对cache接的redis实现类,该类存在mybatis-redis包 中 实现: 1. pom件 2.配置件 Mapper.xml 3.redis.properties 4.测试 org.mybatis.caches mybatis-redis 1.0.0-beta2 select * from userredis.host=localhost redis.port=6379 redis.connectionTimeout=5000 redis.password= redis.database=0 @Test public void SecondLevelCache(){ SqlSession sqlSession1 = sqlSessionFactory.openSession(); 源码分析: RedisCache和家普遍实现Mybatis的缓存案同异,是实现Cache接,并使jedis操作缓 存;不过该项在设计细节上有些区别; RedisCache在mybatis启动的时候,由MyBatis的CacheBuilder创建,创建的式很简单,就是调 RedisCache的带有String参数的构造法,即RedisCache(String id);在RedisCache的构造法中, 调了 RedisConfigu rationBuilder 来创建 RedisConfig 对象,并使 RedisConfig 来创建JedisPool。 RedisConfig类继承了 JedisPoolConfig,并提供了 host,port等属性的包装,简单看下RedisConfig的 属性: SqlSession sqlSession2 = sqlSessionFactory.openSession(); SqlSession sqlSession3 = sqlSessionFactory.openSession(); IUserMapper mapper1 = sqlSession1.getMapper(IUserMapper.class); lUserMapper mapper2 = sqlSession2.getMapper(lUserMapper.class); lUserMapper mapper3 = sqlSession3.getMapper(IUserMapper.class); User user1 = mapper1.findUserById(1); sqlSession1.close(); //清空级缓存 User user = new User(); user.setId(1); user.setUsername(“lisi”); mapper3.updateUser(user); sqlSession3.commit(); User user2 = mapper2.findUserById(1); System.out.println(user1==user2); } public final class RedisCache implements Cache { public RedisCache(final String id) { if (id == null) { throw new IllegalArgumentException(“Cache instances require anID”); } 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()); } RedisConfig对象是由RedisConfigurationBuilder创建的,简单看下这个类的主要法: 核的法就是parseConfiguration法,该法从classpath中读取个redis.properties件: 并将该配置件中的内容设置到RedisConfig对象中,并返回;接下来,就是RedisCache使 RedisConfig类创建完成edisPool;在RedisCache中实现了个简单的模板法,来操作Redis: public class RedisConfig extends JedisPoolConfig { private String host = Protocol.DEFAULT_HOST; private int port = Protocol.DEFAULT_PORT; private int connectionTimeout = Protocol.DEFAULT_TIMEOUT; private int soTimeout = Protocol.DEFAULT_TIMEOUT; private String password; private int database = Protocol.DEFAULT_DATABASE; private String clientName; public RedisConfig parseConfiguration(ClassLoader classLoader) { Properties config = new Properties(); InputStream input = classLoader.getResourceAsStream(redisPropertiesFilename); if (input != null) { try { config.load(input); } catch (IOException e) { throw new RuntimeException( “An error occurred while reading classpath property ‘” + redisPropertiesFilename + “‘, see nested exceptions”, e); } finally { try { input.close(); } catch (IOException e) { // close quietly } } } RedisConfig jedisConfig = new RedisConfig(); setConfigProperties(config, jedisConfig); return jedisConfig; } host=localhost port=6379 connectionTimeout=5000 soTimeout=5000 password= database=0 clientName= 模板接为RedisCallback,这个接中就只需要实现了个doWithRedis法已: 接下来看看Cache中最重要的两个法:putObject和getObject,通过这两个法来查看mybatis-redis 储存数据的格式: 可以很清楚的看到,mybatis-redis在存储数据的时候,是使的hash结构,把cache的id作为这个hash 的key (cache的id在mybatis中就是mapper的namespace);这个mapper中的查询缓存数据作为 hash 的field,需要缓存的内容直接使SerializeUtil存储,SerializeUtil和其他的序列化类差不多,负责 对象 的序列化和反序列化; private Object execute(RedisCallback callback) { Jedis jedis = pool.getResource(); try { return callback.doWithRedis(jedis); } finally { jedis.close(); } } public interface RedisCallback { Object doWithRedis(Jedis jedis); } @Override public void putObject(final Object key, final Object value) { execute(new RedisCallback() { @Override public Object doWithRedis(Jedis jedis) { jedis.hset(id.toString().getBytes(), key.toString().getBytes(), SerializeUtil.serialize(value)); return null; } }); } @Override public Object getObject(final Object key) { return execute(new RedisCallback() { @Override public Object doWithRedis(Jedis jedis) { return SerializeUtil.unserialize(jedis.hget(id.toString().getBytes(), key.toString().getBytes())); } }); } 第部分: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 Mybatis插件原理 在四对象创建的时候 1、每个创建出来的对象不是直接返回的,是interceptorChain.pluginAll(parameterHandler); 2、获取到所有的Interceptor (拦截器)(插件需要实现的接);调 interceptor.plugin(target);返 回 target 包装后的对象 3、插件机制,我们可以使插件为标对象创建个代理对象;AOP (向切)我们的插件可 以 为四对象创建出代理对象,代理对象就可以拦截到四对象的每个执; 拦截 插件具体是如何拦截并附加额外的功能的呢?以ParameterHandler来说 interceptorChain保存了所有的拦截器(interceptors),是mybatis初始化的时候创建的。调拦截器链 中的拦截器依次的对标进拦截或增强。interceptor.plugin(target)中的target就可以理解为mybatis 中的四对象。返回的target是被重重代理后的对象 如果我们想要拦截Executor的query法,那么可以这样定义插件: 除此之外,我们还需将插件配置到sqlMapConfig.xm l中。 public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object object, BoundSql sql, InterceptorChain interceptorChain){ ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement,object,sql); parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); return parameterHandler; } public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; } @Intercepts({ @Signature( type = Executor.class, method = “query”, args= {MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class} ) }) public class ExeunplePlugin implements Interceptor { //省略逻辑 } 这样MyBatis在启动时可以加载插件,并保存插件实例到相关对象(InterceptorChain,拦截器链) 中。 待准备作做完后,MyBatis处于就绪状态。我们在执SQL时,需要先通过DefaultSqlSessionFactory 创建 SqlSession。Executor 实例会在创建 SqlSession 的过程中被创建, Executor实例创建完毕后, MyBatis会通过JDK动态代理为实例成代理类。这样,插件逻辑即可在 Executor相关法被调前执 。 以上就是MyBatis插件机制的基本原理 8.4 定义插件 8.4.1 插件接 Mybatis 插件接-Interceptor Intercept法,插件的核法 plugin法,成target的代理对象 setProperties法,传递插件所需参数 8.4.2定义插件 设计实现个定义插件 Intercepts ({//注意看这个花括号,也就这说这可以定义多个@Signature对多个地拦截,都 这个拦截器 @Signature (type = StatementHandler .class , //这是指拦截哪个接 method = “prepare”,//这个接内的哪个法名,不要拼错了 args = { Connection.class, Integer .class}),//// 这是拦截的法的参,按 顺序写到这,不要多也不要少,如果法重载,可是要通过法名和参来确定唯的 }) public class MyPlugin implements Interceptor { private final Logger logger = LoggerFactory.getLogger(this.getClass()); // //这是每次执操作的时候,都会进这个拦截器的法内 Override public Object intercept(Invocation invocation) throws Throwable { //增强逻辑 System.out.println(“对法进了增强….”); return invocation.proceed(); //执原法 } /** sqlMapConfig.xml mapper接 mapper.xml 测试类 * //主要是为了把这个拦截器成个代理放到拦截器链中 * ^Description包装标对象 为标对象创建代理对象 * @Param target为要拦截的对象 * @Return代理对象 */ Override public Object plugin(Object target) { System.out.println(“将要包装的标对象:”+target); return Plugin.wrap(target,this); } /**获取配置件的属性**/ //插件初始化的时候调,也只调次,插件配置的属性从这设置进来 Override public void setProperties(Properties properties) { System.out.println(“插件配置的初始化参数:”+properties ); } } public interface UserMapper { List selectUser(); }SELECT id,username FROM user8.5 源码分析 执插件逻辑 Plugin实现了 InvocationHandler接,因此它的invoke法会拦截所有的法调。invoke法会 对 所拦截的法进检测,以决定是否执插件逻辑。该法的逻辑如下: public class PluginTest { @Test public void test() throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream(“sqlMapConfig.xml”); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List byPaging = userMapper.selectUser(); for (User user : byPaging) { System.out.println(user); } } } // -Plugin public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { /* *获取被拦截法列表,如: * signatureMap.get(Executor.class), 可能返回 [query, update, commit] */ Set methods = signatureMap.get(method.getDeclaringClass()); //检测法列表是否包含被拦截的法 if (methods != null && methods.contains(method)) { //执插件逻辑 return interceptor.intercept(new Invocation(target, method, args)); //执被拦截的法 return method.invoke(target, args); } catch(Exception e){ } } invoke法的代码较少,逻辑不难理解。先,invoke法会检测被拦截法是否配置在插件的 @Signature注解中,若是,则执插件逻辑,否则执被拦截法。插件逻辑封装在intercept中,该 法的参数类型为Invocationo Invocation主要于存储标类,法以及法参数列表。下简单看 下该类的定义 关于插件的执逻辑就分析结束 8.6 pageHelper分插件 MyBati s可以使第三的插件来对功能进扩展,分助PageHelper是将分的复杂操作进封 装,使简单的式即可获得分的相关数据 开发步骤: ① 导通PageHelper的坐标 ② 在mybatis核配置件中配置PageHelper插件 ③ 测试分数据获取 ①导通PageHelper坐标 ② 在mybatis核配置件中配置PageHelper插件 public class Invocation { private final Object target; private final Method method; private final Object[] args; public Invocation(Object targetf Method method, Object[] args) { this.target = target; this.method = method; //省略部分代码 public Object proceed() throws InvocationTargetException, IllegalAccessException { //调被拦截的法 >> — com.github.pagehelper pagehelper 3.7.5 com.github.jsqlparser jsqlparser 0.9.1 ③ 测试分代码实现 获得分相关的其他参数 8.7 通 mapper 什么是通Mapper 通Mapper就是为了解决单表增删改查,基于Mybatis的插件机制。开发员不需要编写SQL,不需要 在DAO中增加法,只要写好实体类,就能持相应的增删改查法 如何使 1. 先在maven项,在pom.xml中引mapper的依赖 * @Test public void testPageHelper() { //设置分参数 PageHelper.startPage(1, 2); List select = userMapper2.select(null); for (User user : select) { System.out.println(user); } } } //其他分的数据 PageInfo pageInfo = new PageInfo(select); System.out.println(“总条数:”+pageInfo.getTotal()); System.out.println(“总数:”+pageInfo. getPages ()); System.out.println(“当前:”+pageInfo. getPageNum()); System.out.println(“每显万度:”+pageInfo.getPageSize()); System.out.println(“是否第:”+pageInfo.isIsFirstPage()); System.out.println(“是否最后:”+pageInfo.isIsLastPage()); tk.mybatis mapper 3.1.2 2. Mybatis配置件中完成配置 3. 实体类设置主键 4. 定义通mapper 5. 测试 @Table(name = “t_user”) public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String username; } import com.lagou.domain.User; import tk.mybatis.mapper.common.Mapper; public interface UserMapper extends Mapper { } public class UserTest { @Test public void test1() throws IOException { Inputstream resourceAsStream = Resources.getResourceAsStream(“sqlMapConfig.xml”); SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = build.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = new User(); user.setId(4); //(1)mapper基础接 //select 接 User user1 = userMapper.selectOne(user); //根据实体中的属性进查询,只能有 —个返回值 List users = userMapper.select(null); //查询全部结果 userMapper.selectByPrimaryKey(1); //根据主键字段进查询,法参数必须包含完 整的主键属性,查询条件使等号 userMapper.selectCount(user); //根据实体中的属性查询总数,查询条件使等号 // insert 接 int insert = userMapper.insert(user); //保存个实体,null值也会保存,不会使 数据库默认值 int i = userMapper.insertSelective(user); //保存实体,null的属性不会保存, 会使数据库默认值 // update 接 int i1 = userMapper.updateByPrimaryKey(user);//根据主键更新实体全部字段, null值会被更新 // delete 接 int delete = userMapper.delete(user); //根据实体属性作为条件进删除,查询条 件 使等号 userMapper.deleteByPrimaryKey(1); //根据主键字段进删除,法参数必须包含完 整的主键属性 //(2)example法 Example example = new Example(User.class); example.createCriteria().andEqualTo(“id”, 1); example.createCriteria().andLike(“val”, “1”); //定义查询 List users1 = userMapper.selectByExample(example); } } 第九部分:Mybatis架构原理 9.1架构设计 我们把Mybatis的功能架构分为三层: (1) API接层:提供给外部使的接 API,开发员通过这些本地API来操纵数据库。接层接收 到 调请求就会调数据处理层来完成具体的数据处理。 MyBatis和数据库的交互有两种式: a. 使传统的MyBati s提供的API ; b. 使Mapper代理的式 (2) 数据处理层:负责具体的SQL查找、SQL解析、SQL执和执结果映射处理等。它主要的的是根 据调的请求完成次数据库操作。 (3) 基础撑层:负责最基础的功能撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是 共 的东,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的撑 9.2主要构件及其相互关系 构件 描述 SqlSession 作为MyBatis作的主要顶层API,表示和数据库交互的会话,完成必要数 据库增删改查功能 Executor MyBatis执器,是MyBatis调度的核,负责SQL语句的成和查询缓 存的维护 StatementHandler 封装了JDBC Statement操作,负责对JDBC statement的操作,如设置参 数、将Statement结果集转换成List集合。 ParameterHandler 负责对户传递的参数转换成JDBC Statement所需要的参数, ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合; TypeHandler 负责java数据类型和jdbc数据类型之间的映射和转换 MappedStatement MappedStatement维护了条<select | update | delete | insert>节点 的封 装 SqlSource 负责根据户传递的parameterObject,动态地成SQL语句,将信息封 装到BoundSql对象中,并返回 BoundSql 表示动态成的SQL语句以及相应的参数信息 9.3总体流程 (1) 加载配置并初始化 触发条件:加载配置件 配置来源于两个地,个是配置件(主配置件conf.xml,mapper件*.xml),—个是java代码中的 注解,将主配置件内容解析封装到Configuration,将sql的配置信息加载成为个mappedstatement 对象,存储在内存之中 (2) 接收调请求 触发条件:调Mybatis提供的API 传参数:为SQL的ID和传参数对象 处理过程:将请求传递给下层的请求处理层进处理。 (3) 处理操作请求 触发条件:API接层传递请求过来 传参数:为SQL的ID和传参数对象 处理过程: (A) 根据SQL的ID查找对应的MappedStatement对象。 (B) 根据传参数对象解析MappedStatement对象,得到最终要执的SQL和执传参数。 (C) 获取数据库连接,根据得到的最终SQL语句和执传参数到数据库执,并得到执结果。 (D) 根据MappedStatement对象中的结果映射配置对得到的执结果进转换处理,并得到最终的处 理 结果。 (E) 释放连接资源。 (4) 返回处理结果 将最终的处理结果返回。 第部分:Mybatis源码剖析 10.1传统式源码剖析: 源码剖析-初始化 进源码分析: Inputstream inputstream = Resources.getResourceAsStream(“mybatisconfig.xml”); //这代码正是初始化作的开始。 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream); // 1.我们最初调的build public SqlSessionFactory build (InputStream inputStream){ //调了重载法 return build(inputStream, null, null); } MyBatis在初始化的时候,会将MyBatis的配置信息全部加载到内存中,使 org.apache.ibatis.session.Configuratio n 实例来维护 下进对配置件解析部分: 先对Configuration对象进介绍: // 2.调的重载法 public SqlSessionFactory build (InputStream inputStream, String environment, Properties properties){ try { // XMLConfigBuilder是专解析mybatis的配置件的类 XMLConfigBuilder parser = new XMLConfigBuilder(inputstream, environment, properties); //这调了个重载法。parser.parse()的返回值是Configuration对象 return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException(“Error building SqlSession.”, e) } Configuration对象的结构和xml配置件的对象乎相同。 回顾下xml中的配置标签有哪些: properties (属性),settings (设置),typeAliases (类型别名),typeHandlers (类型处理 器),objectFactory (对象),mappers (映射器)等 Configuration也有对应的对象属性来封 装它们 也就是说,初始化配置件信息的本质就是创建Configuration对象,将解析的xml数据封装到 Configuration内部属性中 /** * 解析 XML 成 Configuration 对象。 */ public Configuration parse () { //若已解析,抛出BuilderException异常 if (parsed) { throw new BuilderException(“Each XMLConfigBuilder can only be used once.”); } //标记已解析 parsed = true; // 解析 XML configuration 节点 parseConfiguration(parser.evalNode(“/configuration”)); 介绍下 MappedStatement : 作:MappedStatement与Mapper配置件中的个select/update/insert/delete节点相对应。 mapper中配置的标签都被封装到了此对象中,主要途是描述条SQL语句。 return configuration; } /** *解析XML */ private void parseConfiguration (XNode root){ try { //issue #117 read properties first // 解析 标签 propertiesElement(root.evalNode(“properties”)); // 解析〈settings /> 标签 Properties settings = settingsAsProperties(root.evalNode(“settings”)); //加载定义的VFS实现类 loadCustomVfs(settings); // 解析 标签 typeAliasesElement(root.evalNode(“typeAliases”)); //解析标签 pluginElement(root.evalNode(“plugins”)); // 解析 标签 objectFactoryElement(root.evalNode(“objectFactory”)); // 解析 标签 objectWrapperFactoryElement(root.evalNode(“objectWrapperFactory”)); // 解析 标签 reflectorFactoryElement(root.evalNode(“reflectorFactory”)); // 赋值 Configuration 属性 settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 // 解析〈environments /> 标签 environmentsElement(root.evalNode(“environments”)); // 解析 标签 databaseldProviderElement(root.evalNode(“databaseldProvider”)); // 解析 标签 typeHandlerElement(root.evalNode(“typeHandlers”)); //解析标签 mapperElement(root.evalNode(“mappers”)); } catch (Exception e) { throw new BuilderException(“Error parsing SQL Mapper Configuration.Cause:” + e, e); } } 初始化过程:回顾刚开 始介绍的加载配置件的过程中,会对mybatis-config.xm l中的各个标签都进 解析,其中有mappers 标签来引mapper.xml件或者配置mapper接的录。 样的个select标签会在初始化配置件时被解析封装成个MappedStatement对象,然后存储在 Configuration对象的mappedStatements属性中,mappedStatements 是个HashMap,存储时key =全限定类名+法名,value =对应的MappedStatement对象。 在configuration中对应的属性为 在 XMLConfigBuilder 中的处理: 到此对xml配置件的解析就结束了,回到步骤2.中调的重载build法 源码剖析-执SQL流程 先简单介绍SqlSession : SqlSession是个接,它有两个实现类:DefaultSqlSession (默认)和 SqlSessionManager (弃,不做介绍) SqlSession是MyBatis中于和数据库交互的顶层类,通常将它与ThreadLocal绑定,个会话使 个SqlSession,并且在使完毕后需要closeselect * from user where id=#{id}Map<string, mappedstatement=””> mappedStatements = new StrictMap (“Mapped Statements collection”) private void parseConfiguration(XNode root) { try { //省略其他标签的处理 mapperElement(root.evalNode(“mappers”)); } catch (Exception e) { throw new BuilderException(“Error parsing SQL Mapper Configuration. Cause:” + e, e); } } // 5.调的重载法 public SqlSessionFactory build(Configuration config) { //创建了 DefaultSqlSessionFactory 对象,传 Configuration 对象。 return new DefaultSqlSessionFactory(config); } SqlSession中的两个最重要的参数,configuration与初始化时的相同,Executor为执器 Executor: Executor也是个接,他有三个常的实现类: BatchExecutor (重语句并执批量更新) ReuseExecutor (重预处理语句 prepared statements) SimpleExecutor (普通的执器,默认) 继续分析,初始化完毕后,我们就要执SQL 了 获得 sqlSession public class DefaultSqlSession implements SqlSession { private final Configuration configuration; private final Executor executor; j SqlSession sqlSession = factory.openSession(); List list = sqlSession.selectList(“com.lagou.mapper.UserMapper.getUserByName”); //6. 进 o penSession 法。 public SqlSession openSession() { //getDefaultExecutorType()传递的是SimpleExecutor return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); } //7. 进penSessionFromDataSource。 //ExecutorType 为Executor的类型,TransactionIsolationLevel为事务隔离级别, autoCommit是否开启事务 //openSession的多个重载法可以指定获得的SeqSession的Executor类型和事务的处理 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try{ final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); //根据参数创建指定类型的Executor final Executor executor = configuration.newExecutor(tx, execType); //返回的是 DefaultSqlSession 执 sqlsession 中的 api 源码剖析-executor 继续源码中的步骤,进executor.query() return new DefaultSqlSession(configuration, executor, autoCommit); } catch(Exception e){ closeTransaction(tx); // may have fetched a connection so lets call close() } //8.进selectList法,多个重载法。 public List < E > selectList(String statement) { return this.selectList(statement, null); public List < E > selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT); public List < E > selectList(String statement, Object parameter, RowBounds rowBounds) { try { //根据传的全限定名+法名从映射的Map中取出MappedStatement对象 MappedStatement ms = configuration.getMappedStatement(statement); //调Executor中的法处理 //RowBounds是来逻辑分 // wrapCollection(parameter)是来装饰集合或者数组参数 return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException(“Error querying database. Cause: + e, e); } finally { ErrorContext.instance().reset(); } //此法在SimpleExecutor的类BaseExecutor中实现 public List 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); } //进query的重载法中 public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity(“executing a query”).object(ms.getId()); if (closed) { throw new ExecutorException(“Executor was closed.”); } if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List list; try { queryStack++; list = resultHandler == null ? (List) localCache.getObject(key) : null; if (list != null) { handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { //如果缓存中没有本次查找的值,那么从数据库中查询 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack–; } if (queryStack == 0) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list; } //从数据库查询 private List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List list; 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; } // SimpleExecutor中实现类的doQuery抽象法 public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); //传参数创建StatementHanlder对象来执查询 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); //创建jdbc中的statement对象 stmt = prepareStatement(handler, ms.getStatementLog()); // StatementHandler 进处理 return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } } //创建Statement的法 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; //条代码中的getConnection法经过重重调最后会调openConnection法,从连接池 中获 得连接。 Connection connection = getConnection(statementLog); stmt = handler.prepare(connection, transaction.getTimeout()); handler.parameterize(stmt); return stmt; } //从连接池获得连接的法 protected void openConnection() throws SQLException { if (log.isDebugEnabled()) { log.debug(“Opening JDBC Connection”); } //从连接池获得连接 上述的Executor.query()法经转折,最后会创建个StatementHandler对象,然后将必要的参数传 递给 StatementHandler,使StatementHandler来完成对数据库的查询,最终返回List结果集。 从上的代码中我们可以看出,Executor的功能和作是: 源码剖析-StatementHandler StatementHandler对象主要完成两个作: 对于JDBC的PreparedStatement类型的对象,创建的过程中,我们使的是SQL语句字符串会包 含若个?占位符,我们其后再对占位符进设值。StatementHandler通过 parameterize(statement)法对 S tatement 进设值; StatementHandler 通过 List query(Statement statement, ResultHandler resultHandler)法来 完成执Statement,和将Statement对象返回的resultSet封装成List; 进到 StatementHandler 的 parameterize(statement)法的实现: connection = dataSource.getConnection(); if (level != null) { connection.setTransactionIsolation(level.getLevel()); } } (1、根据传递的参数,完成SQL语句的动态解析,成BoundSql对象,供StatementHandler使; (2、为查询创建缓存,以提性能 (3、创建JDBC的Statement连接对象,传递给*StatementHandler*对象,返回List查询结果。 public void parameterize(Statement statement) throws SQLException { //使ParameterHandler对象来完成对Statement的设值 parameterHandler.setParameters((PreparedStatement) statement); } /** ParameterHandler 类的 setParameters(PreparedStatement ps) 实现 * 对某个Statement进设置参数 * */ public void setParameters(PreparedStatement ps) throws SQLException { ErrorContext.instance().activity(“setting parameters”).object(mappedStatement.getParameterMap().getId()); List parameterMappings = boundSql.getParameterMappings(); if (parameterMappings != null) { for (int i = 0; i < parameterMappings.size(); i++) { ParameterMapping parameterMapping = parameterMappings.get(i); if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; 从上述的代码可以看到,StatementHandler的parameterize(Statement)法调了 ParameterHandler的setParameters(statement)法, ParameterHandler的setParameters(Statement )法负责根据我们输的参数,对statement对象的 ?占位符处进赋值。 进到StatementHandler 的 List query(Statement statement, ResultHandler resultHandler)法的 实现: 从上述代码我们可以看出,StatementHandler 的List query(Statement statement, ResultHandler resultHandler)法的实现,是调了 ResultSetHandler 的 handleResultSets(Statement)法。 String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } // 每个 Mapping都有个 TypeHandler,根据 TypeHandler 来对 preparedStatement 进 设置参数 TypeHandler typeHandler = parameterMapping.getTypeHandler(); JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null) jdbcType = configuration.getJdbcTypeForNull(); //设置参数 typeHandler.setParameter(ps, i + 1, value, jdbcType); } } } } public List query(Statement statement, ResultHandler resultHandler) throws SQLException { // 1.调preparedStatemnt。execute()法,然后将resultSet交给ResultSetHandler处 理 PreparedStatement ps = (PreparedStatement) statement; ps.execute(); //2.使 ResultHandler 来处理 ResultSet return resultSetHandler. handleResultSets(ps); } ResultSetHandler 的 handleResultSets(Statement)法会将 Statement 语句执后成的 resultSet 结 果集转换成List结果集 public List handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity(“handling results”).object(mappedStatement.getId()); //多ResultSet的结果集合,每个ResultSet对应个Object对象。实际上,每 个 Object 是 List 对象。 //在不考虑存储过程的多ResultSet的情况,普通的查询,实际就个ResultSet,也 就是说, multipleResults最多就个元素。 final List multipleResults = new ArrayList<>(); int resultSetCount = 0; //获得个ResultSet对象,并封装成ResultSetWrapper对象 ResultSetWrapper rsw = getFirstResultSet(stmt); //获得ResultMap数组 //在不考虑存储过程的多ResultSet的情况,普通的查询,实际就个ResultSet,也 就是 说,resultMaps就个元素。 List resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); // 校验 while (rsw != null && resultMapCount > resultSetCount) { //获得ResultMap对象 ResultMap resultMap = resultMaps.get(resultSetCount); //处理ResultSet,将结果添加到multipleResults中 handleResultSet(rsw, resultMap, multipleResults, null); //获得下个ResultSet对象,并封装成ResultSetWrapper对象 rsw = getNextResultSet(stmt); //清理 cleanUpAfterHandlingResultSet(); // resultSetCount ++ resultSetCount++; } } //因为’mappedStatement.resultSets’只在存储过程中使,本系列暂时不考虑,忽略即可 String[] resultSets = mappedStatement.getResultSets(); if(resultSets!=null) { while (rsw != null && resultSetCount < resultSets.length) { ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]); if (parentMapping != null) { String nestedResultMapId = parentMapping.getNestedResultMapId(); ResultMap resultMap = configuration.getResultMap(nestedResultMapId); handleResultSet(rsw, resultMap, null, parentMapping); 10.2 Mapper代理式: 回顾下写法: 思考个问题,通常的Mapper接我们都没有实现的法却可以使,是为什么呢?答案很简单动态 代理 开始之前介绍下MyBatis初始化时对接的处理:MapperRegistry是Configuration中的个属性, 它内部维护个HashMap于存放mapper接的类,每个接对应个类。mappers中可以 配置接的包路径,或者某个具体的接类。 当解析mappers标签时,它会判断解析到的是mapper配置件时,会再将对应配置件中的增删 改 查标签 封装成MappedStatement对象,存mappedStatements中。(上介绍了)当 判断解析到接时,会 建此接对应的MapperProxyFactory对象,存HashMap中,key =接的字节码对象,value =此接 对应的MapperProxyFactory对象。 源码剖析-getmapper() } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } } //如果是multipleResults单元素,则取元素返回 return collapseSingleResultList(multipleResults); } public static void main(String[] args) { //前三步都相同 InputStream inputStream = Resources.getResourceAsStream(“sqlMapConfig.xml”); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = factory.openSession(); //这不再调SqlSession的api,是获得了接对象,调接中的法。 UserMapper mapper = sqlSession.getMapper(UserMapper.class); List list = mapper.getUserByName(“tom”); } 进 sqlSession.getMapper(UserMapper.class )中 //DefaultSqlSession 中的 getMapper public T getMapper(Class type) { return configuration.getMapper(type, this); } //configuration 中的给 g etMapper public T getMapper(Class type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } //MapperRegistry 中的 g etMapper public T getMapper(Class type, SqlSession sqlSession) { //从 MapperRegistry 中的 HashMap 中拿 MapperProxyFactory final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException(“Type ” + type + ” is not known to the MapperRegistry.”); } try { //通过动态代理成示例。 return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException(“Error getting mapper instance. Cause: ” + e, e); } } //MapperProxyFactory 类中的 newInstance 法 public T newInstance(SqlSession sqlSession) { //创建了 JDK动态代理的Handler类 final MapperProxy mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); //调了重载法 return newInstance(mapperProxy); } //MapperProxy 类,实现了 InvocationHandler 接 public class MapperProxy implements InvocationHandler, Serializable { //省略部分源码 private final SqlSession sqlSession; private final Class mapperInterface; private final Map<method, mappermethod=””> methodCache; //构造,传了 SqlSession,说明每个session中的代理对象的不同的! public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map<method, mappermethod=””> methodCache) { 源码剖析-invoke() 在动态代理返回了示例后,我们就可以直接调mapper类中的法了,但代理对象调法,执是 在MapperProxy中的invoke法中 进execute法: this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; } //省略部分源码 } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { //如果是Object定义的法,直接调 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } // 获得 MapperMethod 对象 final MapperMethod mapperMethod = cachedMapperMethod(method); //重点在这:MapperMethod最终调了执的法 return mapperMethod.execute(sqlSession, args); } public Object execute(SqlSession sqlSession, Object[] args) { Object result; //判断mapper中的法类型,最终调的还是SqlSession中的法 switch (command.getType()) { case INSERT: { //转换参数 Object param = method.convertArgsToSqlCommandParam(args); //执INSERT操作 // 转换 rowCount result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { //转换参数 Object param = method.convertArgsToSqlCommandParam(args); // 转换 rowCount result = rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { //转换参数 Object param = method.convertArgsToSqlCommandParam(args); // 转换 rowCount result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } case SELECT: //返回,并且有ResultHandler法参数,则将查询的结果,提交给 ResultHandler 进 处理 if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; //执查询,返回列表 } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); //执查询,返回Map } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); //执查询,返回Cursor } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); //执查询,返回单个对象 } else { //转换参数 Object param = method.convertArgsToSqlCommandParam(args); //查询单条 result = sqlSession.selectOne(command.getName(), param); if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException(“Unknown execution method for: ” + command.getName()); 10.3 级缓存源码剖析: 级缓存构建在级缓存之上,在收到查询请求时,MyBatis 先会查询级缓存,若级缓存未命 中,再去查询级缓存,级缓存没有,再查询数据库。 级缓存——》 级缓存——》数据库 与级缓存不同,级缓存和具体的命名空间绑定,个Mapper中有个Cache,相同Mapper中的 MappedStatement共个Cache,级缓存则是和 SqlSession 绑定。 启级缓存 分为三步: 1)开启全局级缓存配置: 2) 在需要使级缓存的Mapper配置件中配置标签 3)在具体CURD标签上配置 useCache=true 标签 < cache/> 的解析 根据之前的mybatis源码剖析,xml的解析作主要交给XMLConfigBuilder.parse()法来实现 } //返回结果为null,并且返回类型为基本类型,则抛出BindingException异常 if(result ==null&&method.getReturnType().isPrimitive() &&!method.returnsVoid()){ throw new BindingException(“Mapper method ‘” + command.getName() + ” attempted to return null from a method with a primitive return type(” + method.getReturnType() + “). “); } //返回结果 return result; }select * from user where id = #{id}// XMLConfigBuilder.parse() public Configuration parse() { if (parsed) { throw new BuilderException(“Each XMLConfigBuilder can only be used once.”); } parsed = true; parseConfiguration(parser.evalNode(“/configuration”));// 在这 return configuration; } // parseConfiguration() // 既然是在xml中添加的,那么我们就直接看关于mappers标签的解析 private void parseConfiguration(XNode root) { try { Properties settings = settingsAsPropertiess(root.evalNode(“settings”)); propertiesElement(root.evalNode(“properties”)); loadCustomVfs(settings); typeAliasesElement(root.evalNode(“typeAliases”)); pluginElement(root.evalNode(“plugins”)); objectFactoryElement(root.evalNode(“objectFactory”)); objectWrapperFactoryElement(root.evalNode(“objectWrapperFactory”)); reflectionFactoryElement(root.evalNode(“reflectionFactory”)); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 environmentsElement(root.evalNode(“environments”)); databaseIdProviderElement(root.evalNode(“databaseIdProvider”)); typeHandlerElement(root.evalNode(“typeHandlers”)); // 就是这 mapperElement(root.evalNode(“mappers”)); } catch (Exception e) { throw new BuilderException(“Error parsing SQL Mapper Configuration. Cause: ” + e, e); } } // mapperElement() private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if (“package”.equals(child.getName())) { String mapperPackage = child.getStringAttribute(“name”); configuration.addMappers(mapperPackage); } else { String resource = child.getStringAttribute(“resource”); String url = child.getStringAttribute(“url”); String mapperClass = child.getStringAttribute(“class”); 我们来看看解析Mapper.xml // 按照我们本例的配置,则直接该if判断 if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); // 成XMLMapperBuilder,并执其parse法 mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { Class mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { throw new BuilderException(“A mapper element may only specify a url, resource or class, but not more than one.”); } } } } } // XMLMapperBuilder.parse() public void parse() { if (!configuration.isResourceLoaded(resource)) { // 解析mapper属性 configurationElement(parser.evalNode(“/mapper”)); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingChacheRefs(); parsePendingStatements(); } // configurationElement() 先来看看是如何构建Cache对象的 MapperBuilderAssistant.useNewCache() private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute(“namespace”); if (namespace == null || namespace.equals(“”)) { throw new BuilderException(“Mapper’s namespace cannot be empty”); } builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode(“cache-ref”)); // 最终在这看到了关于cache属性的处理 cacheElement(context.evalNode(“cache”)); parameterMapElement(context.evalNodes(“/mapper/parameterMap”)); resultMapElements(context.evalNodes(“/mapper/resultMap”)); sqlElement(context.evalNodes(“/mapper/sql”)); // 这会将成的Cache包装到对应的MappedStatement buildStatementFromContext(context.evalNodes(“select|insert|update|delete”)); } catch (Exception e) { throw new BuilderException(“Error parsing Mapper XML. Cause: ” + e, e); } } // cacheElement() private void cacheElement(XNode context) throws Exception { if (context != null) { //解析标签的type属性,这我们可以定义cache的实现类,如redisCache, 如果没有定义,这使和级缓存相同的PERPETUAL String type = context.getStringAttribute(“type”, “PERPETUAL”); Class typeClass = typeAliasRegistry.resolveAlias(type); String eviction = context.getStringAttribute(“eviction”, “LRU”); Class evictionClass = typeAliasRegistry.resolveAlias(eviction); Long flushInterval = context.getLongAttribute(“flushInterval”); Integer size = context.getIntAttribute(“size”); boolean readWrite = !context.getBooleanAttribute(“readOnly”, false); boolean blocking = context.getBooleanAttribute(“blocking”, false); Properties props = context.getChildrenAsProperties(); // 构建Cache对象 builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props); } } public Cache useNewCache(Class typeClass, 我们看到个Mapper.xml只会解析次标签,也就是只创建次Cache对象,放进configuration中, 并将cache赋值给MapperBuilderAssistant.currentCache buildStatementFromContext(context.evalNodes(“select|insert|update|delete”));将Cache 包装到MappedStatement Class evictionClass, Long flushInterval, Integer size, boolean readWrite, boolean blocking, Properties props) { // 1.成Cache对象 Cache cache = new CacheBuilder(currentNamespace) //这如果我们定义了中的type,就使定义的Cache,否则使和级缓存相 同的PerpetualCache .implementation(valueOrDefault(typeClass, PerpetualCache.class)) .addDecorator(valueOrDefault(evictionClass, LruCache.class)) .clearInterval(flushInterval) .size(size) .readWrite(readWrite) .blocking(blocking) .properties(props) .build(); // 2.添加到Configuration中 configuration.addCache(cache); // 3.并将cache赋值给MapperBuilderAssistant.currentCache currentCache = cache; return cache; } // buildStatementFromContext() private void buildStatementFromContext(List list) { if (configuration.getDatabaseId() != null) { buildStatementFromContext(list, configuration.getDatabaseId()); } buildStatementFromContext(list, null); } //buildStatementFromContext() private void buildStatementFromContext(List list, String requiredDatabaseId) { for (XNode context : list) { final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { // 每条执语句转换成个MappedStatement statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } } } // XMLStatementBuilder.parseStatementNode(); public void parseStatementNode() { String id = context.getStringAttribute(“id”); String databaseId = context.getStringAttribute(“databaseId”); … Integer fetchSize = context.getIntAttribute(“fetchSize”); Integer timeout = context.getIntAttribute(“timeout”); String parameterMap = context.getStringAttribute(“parameterMap”); String parameterType = context.getStringAttribute(“parameterType”); Class parameterTypeClass = resolveClass(parameterType); String resultMap = context.getStringAttribute(“resultMap”); String resultType = context.getStringAttribute(“resultType”); String lang = context.getStringAttribute(“lang”); LanguageDriver langDriver = getLanguageDriver(lang); … // 创建MappedStatement对象 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); } // builderAssistant.addMappedStatement() public MappedStatement addMappedStatement( String id, …) { if (unresolvedCacheRef) { throw new IncompleteElementException(“Cache-ref not yet resolved”); } id = applyCurrentNamespace(id, false); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; //创建MappedStatement对象 MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType) … 我们看到将Mapper中创建的Cache对象,加到了每个MappedStatement对象中,也就是同个 Mapper中所有的2 有关于标签的解析就到这了。 查询源码分析 CachingExecutor .flushCacheRequired(valueOrDefault(flushCache, !isSelect)) .useCache(valueOrDefault(useCache, isSelect)) .cache(currentCache);// 在这将之前成的Cache封装到MappedStatement ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id); if (statementParameterMap != null) { statementBuilder.parameterMap(statementParameterMap); } MappedStatement statement = statementBuilder.build(); configuration.addMappedStatement(statement); return statement; } // CachingExecutor public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); // 创建 CacheKey CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { // 从 MappedStatement 中获取 Cache,注意这的 Cache 是从MappedStatement中获取的 // 也就是我们上解析Mapper中标签中创建的,它保存在Configration中 // 我们在上解析blog.xml时分析过每个MappedStatement都有个Cache对象,就是这 Cache cache = ms.getCache(); // 如果配置件中没有配置 ,则 cache 为空 if (cache != null) { //如果需要刷新缓存的话就刷新:flushCache=”true” flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, boundSql); // 访问级缓存 List list = (List) tcm.getObject(cache, key); // 缓存未命中 如果设置了flushCache=”true”,则每次查询都会刷新缓存 如上,注意级缓存是从 MappedStatement 中获取的。由于 MappedStatement 存在于全局配置 中,可以多个 CachingExecutor 获取到,这样就会出现线程安全问题。除此之外,若不加以控制,多个 事务共个缓存实例,会导致脏读问题。于脏读问题,需要借助其他类来处理,也就是上代码中 tcm 变量对应的类型。下分析下。 TransactionalCacheManager if (list == null) { // 如果没有值,则执查询,这个查询实际也是先级缓存查询,级缓存也没 有的话,则进DB查询 list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); // 缓存查询结果 tcm.putObject(cache, key, list); } return list; } } return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } select * from t_demo/** 事务缓存管理器 */ public class TransactionalCacheManager { // Cache 与 TransactionalCache 的映射关系表 private final Map<cache, transactionalcache=””> transactionalCaches = new HashMap<cache, transactionalcache=””>(); public void clear(Cache cache) { // 获取 TransactionalCache 对象,并调该对象的 clear 法,下同 getTransactionalCache(cache).clear(); } public Object getObject(Cache cache, CacheKey key) { // 直接从TransactionalCache中获取缓存 return getTransactionalCache(cache).getObject(key); } public void putObject(Cache cache, CacheKey key, Object value) { // 直接存TransactionalCache的缓存中 TransactionalCacheManager 内部维护了 Cache 实例与 TransactionalCache 实例间的映射关系,该类 也仅负责维护两者的映射关系,真正做事的还是 TransactionalCache。TransactionalCache 是种缓 存装饰器,可以为 Cache 实例增加事务功能。我在之前提到的脏读问题正是由该类进处理的。下分 析下该类的逻辑。 TransactionalCache getTransactionalCache(cache).putObject(key, value); } public void commit() { for (TransactionalCache txCache : transactionalCaches.values()) { txCache.commit(); } } public void rollback() { for (TransactionalCache txCache : transactionalCaches.values()) { txCache.rollback(); } } private TransactionalCache getTransactionalCache(Cache cache) { // 从映射表中获取 TransactionalCache TransactionalCache txCache = transactionalCaches.get(cache); if (txCache == null) { // TransactionalCache 也是种装饰类,为 Cache 增加事务功能 // 创建个新的TransactionalCache,并将真正的Cache对象存进去 txCache = new TransactionalCache(cache); transactionalCaches.put(cache, txCache); } return txCache; } } public class TransactionalCache implements Cache { //真正的缓存对象,和上的Map<cache, transactionalcache=””>中的Cache是同个 private final Cache delegate; private boolean clearOnCommit; // 在事务被提交前,所有从数据库中查询的结果将缓存在此集合中 private final Map<object, object=””> entriesToAddOnCommit; // 在事务被提交前,当缓存未命中时,CacheKey 将会被存储在此集合中 private final Set entriesMissedInCache; @Override public Object getObject(Object key) { // 查询的时候是直接从delegate中去查询的,也就是从真正的缓存对象中查询 Object object = delegate.getObject(key); if (object == null) { // 缓存未命中,则将 key 存到 entriesMissedInCache 中 entriesMissedInCache.add(key); } if (clearOnCommit) { return null; } else { return object; } } @Override public void putObject(Object key, Object object) { // 将键值对存到 entriesToAddOnCommit 这个Map中中,真实的缓存对象 delegate 中 entriesToAddOnCommit.put(key, object); } @Override public Object removeObject(Object key) { return null; } @Override public void clear() { clearOnCommit = true; // 清空 entriesToAddOnCommit,但不清空 delegate 缓存 entriesToAddOnCommit.clear(); } public void commit() { // 根据 clearOnCommit 的值决定是否清空 delegate if (clearOnCommit) { delegate.clear(); } // 刷新未缓存的结果到 delegate 缓存中 flushPendingEntries(); // 重置 entriesToAddOnCommit 和 entriesMissedInCache reset(); } public void rollback() { unlockMissedEntries(); reset(); } private void reset() { 存储级缓存对象的时候是放到了TransactionalCache.entriesToAddOnCommit这个map中,但是每 次查询的时候是直接从TransactionalCache.delegate中去查询的,所以这个级缓存查询数据库后,设 置缓存值是没有刻效的,主要是因为直接存到 delegate 会导致脏数据问题 为何只有SqlSession提交或关闭之后? 那我们来看下SqlSession.commit()法做了什么 SqlSession clearOnCommit = false; // 清空集合 entriesToAddOnCommit.clear(); entriesMissedInCache.clear(); } private void flushPendingEntries() { for (Map.Entry<object, object=””> entry : entriesToAddOnCommit.entrySet()) { // 将 entriesToAddOnCommit 中的内容转存到 delegate 中 delegate.putObject(entry.getKey(), entry.getValue()); } for (Object entry : entriesMissedInCache) { if (!entriesToAddOnCommit.containsKey(entry)) { // 存空值 delegate.putObject(entry, null); } } } private void unlockMissedEntries() { for (Object entry : entriesMissedInCache) { try { // 调 removeObject 进解锁 delegate.removeObject(entry); } catch (Exception e) { log.warn(“…”); } } } } @Override public void commit(boolean force) { try { // 主要是这句 executor.commit(isCommitOrRollbackRequired(force)); 级缓存的刷新 我们来看看SqlSession的更新操作 dirty = false; } catch (Exception e) { throw ExceptionFactory.wrapException(“Error committing transaction. Cause: ” + e, e); } finally { ErrorContext.instance().reset(); } } // CachingExecutor.commit() @Override public void commit(boolean required) throws SQLException { delegate.commit(required); tcm.commit();// 在这 } // TransactionalCacheManager.commit() public void commit() { for (TransactionalCache txCache : transactionalCaches.values()) { txCache.commit();// 在这 } } // TransactionalCache.commit() public void commit() { if (clearOnCommit) { delegate.clear(); } flushPendingEntries();//这句 reset(); } // TransactionalCache.flushPendingEntries() private void flushPendingEntries() { for (Map.Entry<object, object=””> entry : entriesToAddOnCommit.entrySet()) { // 在这真正的将entriesToAddOnCommit的对象逐个添加到delegate中,只有这时, 级缓存才真正的效 delegate.putObject(entry.getKey(), entry.getValue()); } for (Object entry : entriesMissedInCache) { if (!entriesToAddOnCommit.containsKey(entry)) { delegate.putObject(entry, null); } } } MyBatis级缓存只适于不常进增、删、改的数据,如国家政区省市区街道数据。但数据变 更,MyBatis会清空缓存。因此级缓存不适于经常进更新的数据。 总结: 在级缓存的设计上,MyBatis量地运了装饰者模式,如CachingExecutor, 以及各种Cache接的 装饰器。 级缓存实现了Sqlsession之间的缓存数据共享,属于namespace级别 级缓存具有丰富的缓存策略。 级缓存可由多个装饰器,与基础缓存组合成 级缓存作由 个缓存装饰执器CachingExecutor和 个事务型预缓存TransactionalCache 完成。 10.4 延迟加载源码剖析: public int update(String statement, Object parameter) { int var4; try { this.dirty = true; MappedStatement ms = this.configuration.getMappedStatement(statement); var4 = this.executor.update(ms, this.wrapCollection(parameter)); } catch (Exception var8) { throw ExceptionFactory.wrapException(“Error updating database. Cause: ” + var8, var8); } finally { ErrorContext.instance().reset(); } return var4; } public int update(MappedStatement ms, Object parameterObject) throws SQLException { this.flushCacheIfRequired(ms); return this.delegate.update(ms, parameterObject); } private void flushCacheIfRequired(MappedStatement ms) { //获取MappedStatement对应的Cache,进清空 Cache cache = ms.getCache(); //SQL需设置flushCache=”true” 才会执清空 if (cache != null && ms.isFlushCacheRequired()) { this.tcm.clear(cache); } } 什么是延迟加载? 问题 在开发过程中很多时候我们并不需要总是在加载户信息时就定要加载他的订单信息。此时就是我 们所说的延迟加载。 举个栗 延迟加载 就是在需要到数据时才进加载,不需要到数据时就不加载数据。延迟加载也称懒加载。 实现 局部延迟加载 在association和collection标签中都有个fetchType属性,通过修改它的值,可以修改局部的加载策 略。 * 在对多中,当我们有个户,它有个100个订单 在查询户的时候,要不要把关联的订单查出来? 在查询订单的时候,要不要把关联的户查出来? * 回答 在查询户时,户下的订单应该是,什么时候,什么时候查询。 在查询订单时,订单所属的户信息应该是随着订单起查询出来。 * 优点: 先从单表查询,需要时再从关联表去关联查询,提数据库性能,因为查询单表要关联查询多张表 速度要快。 * 缺点: 因为只有当需要到数据时,才会进数据库查询,这样在批量数据查询时,因为查询作也要消耗时 间,所以可能造成户等待时间变,造成户体验下降。 * 在多表中: 对多,多对多:通常情况下采延迟加载 对(多对):通常情况下采即加载 * 注意: 延迟加载是基于嵌套查询来实现的 全局延迟加载 在Mybatis的核配置件中可以使setting标签修改全局的加载策略。 注意 7.。 SELECT * FROM `user` SELECT * from orders延迟加载原理实现 它的原理是,使 CGLIB 或 Javassist( 默认 ) 创建标对象的代理对象。当调代理对象的延迟加载属 性的 getting 法时,进拦截器法。如调 a.getB().getName() 法,进拦截器的 invoke(…) 法,发现 a.getB() 需要延迟加载时,那么就会单独发送事先保存好的查询关联 B 对象的 SQL ,把 B 查询上来,然后调 a.setB(b) 法,于是 a 对象 b 属性就有值了,接着完 成 a.getB().getName() 法的调。这就是延迟加载的基本原理 总结:延迟加载主要是通过动态代理的形式实现,通过代理拦截到指定法,执数据加载。 延迟加载原理(源码剖析) MyBatis延迟加载主要使:Javassist,Cglib实现,类图展示: Setting 配置加载: public class Configuration { /** aggressiveLazyLoading: * 当开启时,任何法的调都会加载该对象的所有属性。否则,每个属性会按需加载(参考 lazyLoadTriggerMethods). * 默认为true * */ protected boolean aggressiveLazyLoading; /** * 延迟加载触发法 */ protected Set lazyLoadTriggerMethods = new HashSet (Arrays.asList(new String[] { “equals”, “clone”, “hashCode”, “toString” })); /** 是否开启延迟加载 */ protected boolean lazyLoadingEnabled = false; /** 延迟加载代理对象创建 Mybatis的查询结果是由ResultSetHandler接的handleResultSets()法处理的。ResultSetHandler 接只有个实现,DefaultResultSetHandler,接下来看下延迟加载相关的个核的法 * 默认使Javassist代理 * @param proxyFactory */ public void setProxyFactory(ProxyFactory proxyFactory) { if (proxyFactory == null) { proxyFactory = new JavassistProxyFactory(); } this.proxyFactory = proxyFactory; } //省略… }

//#mark 创建结果对象 private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { this.useConstructorMappings = false; // reset previous mapping result final List<Class<?>> constructorArgTypes = new ArrayList<Class<?>>(); final List<Object> constructorArgs = new ArrayList<Object>(); //#mark 创建返回的结果映射的真实对象 Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix); if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings(); for (ResultMapping propertyMapping : propertyMappings) { // 判断属性有没配置嵌套查询,如果有就创建代理对象 if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) { //#mark 创建延迟加载代理对象 resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); break; } } } this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result return resultObject; 默认采javassistProxy进代理对象的创建 JavasisstProxyFactory实现 } public class JavassistProxyFactory implements org.apache.ibatis.executor.loader.ProxyFactory { /** * 接实现 * @param target 标结果对象 * @param lazyLoader 延迟加载对象 * @param configuration 配置 * @param objectFactory 对象 * @param constructorArgTypes 构造参数类型 * @param constructorArgs 构造参数值 * @return */ @Override public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<? >> constructorArgTypes, List<Object> constructorArgs) { return EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); } //省略... /** * 代理对象实现,核逻辑执 */ private static class EnhancedResultObjectProxyImpl implements MethodHandler { /** * 创建代理对象 * @param type * @param callback * @param constructorArgTypes * @param constructorArgs * @return */ static Object crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { ProxyFactory enhancer = new ProxyFactory(); enhancer.setSuperclass(type); try { //通过获取对象法,判断是否存在该法 type.getDeclaredMethod(WRITE_REPLACE_METHOD); // ObjectOutputStream will call writeReplace of objects returned by writeReplace if (log.isDebugEnabled()) { log.debug(WRITE_REPLACE_METHOD + " method was found on bean " + type + ", make sure it returns this"); } } catch (NoSuchMethodException e) { //没找到该法,实现接 enhancer.setInterfaces(new Class[]{WriteReplaceInterface.class}); } catch (SecurityException e) { // nothing to do here } Object enhanced; Class<?>[] typesArray = constructorArgTypes.toArray(new Class[constructorArgTypes.size()]); Object[] valuesArray = constructorArgs.toArray(new Object[constructorArgs.size()]); try { //创建新的代理对象 enhanced = enhancer.create(typesArray, valuesArray); } catch (Exception e) { throw new ExecutorException("Error creating lazy proxy. Cause: " + e, e); } //设置代理执器 ((Proxy) enhanced).setHandler(callback); return enhanced; } /** * 代理对象执 * @param enhanced 原对象 * @param method 原对象法 * @param methodProxy 代理法 * @param args 法参数 * @return * @throws Throwable */ @Override public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable { final String methodName = method.getName(); try { synchronized (lazyLoader) { if (WRITE_REPLACE_METHOD.equals(methodName)) { //忽略暂未找到具体作 Object original; if (constructorArgTypes.isEmpty()) { original = objectFactory.create(type); } else { original = objectFactory.create(type, constructorArgTypes, constructorArgs); } PropertyCopier.copyBeanProperties(type, enhanced, original); if (lazyLoader.size() > 0) { return new JavassistSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs); } else { return original; } } else { //延迟加载数量于0 if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) { //aggressive 次加载性所有需要要延迟加载属性或者包含触发延迟加载法 if (aggressive || lazyLoadTriggerMethods.contains(methodName)) { log.debug("==> laze lod trigger method:" + methodName + ",proxy method:" + methodProxy.getName() + " class:" + enhanced.getClass()); //次全部加载 lazyLoader.loadAll(); } else if (PropertyNamer.isSetter(methodName)) { //判断是否为set法,set法不需要延迟加载 final String property = PropertyNamer.methodToProperty(methodName); lazyLoader.remove(property); } else if (PropertyNamer.isGetter(methodName)) { final String property = PropertyNamer.methodToProperty(methodName); if (lazyLoader.hasLoader(property)) { //延迟加载单个属性 lazyLoader.load(property); log.debug("load one :" + methodName); } } 注意事项 1. IDEA调试问题 当配置aggressiveLazyLoading=true,在使IDEA进调试的时候,如果断点打到 代理执逻辑当中,你会发现延迟加载的代码永远都不能进,总是会被提前执。 主要产的 原因在aggressiveLazyLoading,因为在调试的时候,IDEA的Debuger窗体中已经触发了延迟加载 对象的法。 第部分:设计模式 虽然我们都知道有3类23种设计模式,但是多停留在概念层,Mybatis源码中使了量的设计模 式,观察设计模式在其中的应,能够更深的理解设计模式 Mybati s少到了以下的设计模式的使: } } } return methodProxy.invoke(enhanced, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } } 模式 mybatis 体现 Builder 模式 例如SqlSessionFactoryBuilder、Environment;  法模式 例如SqlSessionFactory、TransactionFactory、LogFactory 单例模 式 例如 ErrorContext 和 LogFactory; 代理模 式 Mybatis实现的核,如MapperProxy、ConnectionLogger,的jdk的动态代理 还有executor.loader包使了 cglib或者javassist达到延迟加载的效果 组合模 式 例如SqlNode和各个类ChooseSqlNode等; 模板 法模式 例如 BaseExecutor 和 SimpleExecutor,还有 BaseTypeHandler 和所有的类例如 IntegerTypeHandler; 适配器 模式 例如Log的Mybatis接和它对jdbc、log4j等各种志框架的适配实现; 装饰者 模式 例如Cache包中的cache.decorators包中等各个装饰者的实现; 迭代器 模式 例如迭代器模式PropertyTokenizer; 接下来对Builder构建者模式、模式、代理模式进解读,先介绍模式身的知识,然后解读在 Mybatis中怎样应了该模式。 11.1 Builder构建者模式 Builder模式的定义是"将个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表 示。”,它属于创建类模式,般来说,如果个对象的构建较复杂,超出了构造函数所能包含的范 围,就可以使模式和Builder模式,相对于模式会产出个完整的产品,Builder应于更加 复杂的对象的构建,甚只会构建产品的个部分,直来说,就是使多个简单的对象步步构建 成个复杂的对象 例:使构建者设计模式来产computer 主要步骤: 1、将需要构建的标类分成多个部件(电脑可以分为主机、显示器、键盘、箱等部件); 2、 创建构建类; 3、 依次创建部件; 4、 将部件组装成标对象 1. 定义computer ComputerBuilder package com.lagou.dao; import org.apache.ibatis.binding.BindingException; import org.apache.ibatis.session.SqlSession; import java.util.Optional; public class Computer { private String displayer; private String mainUnit; private String mouse; private String keyboard; public String getDisplayer() { return displayer; } public void setDisplayer(String displayer) { this.displayer = displayer; } public String getMainUnit() { return mainUnit; } public void setMainUnit(String mainUnit) { this.mainUnit = mainUnit; } public String getMouse() { return mouse; } public void setMouse(String mouse) { this.mouse = mouse; } public String getKeyboard() { return keyboard; } public void setKeyboard(String keyboard) { this.keyboard = keyboard; } @Override public String toString() { return "Computer{" + "displayer='" + displayer + '\'' + ", mainUnit='" + mainUnit + '\'' + ", mouse='" + mouse + '\'' + ", keyboard='" + keyboard + '\'' + '}'; } public static class ComputerBuilder { 调 Mybatis中的体现 SqlSessionFactory 的构建过程: Mybatis的初始化作常复杂,不是只个构造函数就能搞定的。所以使了建造者模式,使了  量的Builder,进分层构造,核对象Configuration使了 XmlConfigBuilder来进构造 private ComputerBuilder target = new ComputerBuilder(); public Builder installDisplayer(String displayer) { target.setDisplayer(displayer); return this; } public Builder installMainUnit(String mainUnit) { target.setMainUnit(mainUnit); return this; } public Builder installMouse(String mouse) { target.setMouse(mouse); return this; } public Builder installKeybord(String keyboard) { target.setKeyboard(keyboard); return this; } public ComputerBuilder build() { return target; } } public static void main(String[]args){ ComputerBuilder computerBuilder=new ComputerBuilder(); computerBuilder.installDisplayer("显万器"); computerBuilder.installMainUnit("主机"); computerBuilder.installKeybord("键盘"); computerBuilder.installMouse("标"); Computer computer=computerBuilder.Builder(); System.out.println(computer); } 在Mybatis环境的初始化过程中,SqlSessionFactoryBuilder会调XMLConfigBuilder读取所有的 MybatisMapConfig.xml 和所有的 *Mapper.xml 件,构建 Mybatis 运的核对象 Configuration 对 象,然后将该Configuration对象作为参数构建个SqlSessionFactory对象。 private void parseConfiguration(XNode root) { try { //issue #117 read properties first //解析标签 propertiesElement(root.evalNode("properties")); // 解析 标签 Properties settings = settingsAsProperties(root.evalNode("settings")); //加载定义的VFS实现类 loadCustomVfs(settings); // 解析 标签 typeAliasesElement(root.evalNode("typeAliases")); //解析标签 pluginElement(root.evalNode("plugins")); // 解析 标签 objectFactoryElement(root.evaINode("obj ectFactory")); // 解析 标签 obj ectWrappe rFacto ryElement(root.evalNode("objectWrapperFactory")); // 解析 标签 reflectorFactoryElement(root.evalNode("reflectorFactory")); // 赋值 到 Configuration 属性 settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 // 解析 标签 environmentsElement(root.evalNode("environments")); // 解析 标签 其中 XMLConfigBuilder 在构建 Configuration 对象时,也会调 XMLMapperBuilder 于读取 *Mapper 件,XMLMapperBuilder会使XMLStatementBuilder来读取和build所有的SQL语句。 在这个过程中,有个相似的特点,就是这些Builder会读取件或者配置,然后做量的XpathParser 解析、配置或语法的解析、反射成对象、存结果缓存等步骤,这么多的作都不是个构造函数所 能包括的,因此量采了 Builder模式来解决 SqlSessionFactoryBuilder类根据不同的输参数来构建SqlSessionFactory这个对象 11.2 模式 在Mybatis中如SqlSessionFactory使的是模式,该没有那么复杂的逻辑,是个简单 模式。 简单模式(Simple Factory Pattern):称为静态法(Static Factory Method)模式,它属于创 建型模式。 在简单模式中,可以根据参数的不同返回不同类的实例。简单模式专定义个类来负责创建 其他类的实例,被创建的实例通常都具有共同的类 例:产电脑 假设有个电脑的代产商,它前已经可以代产联想电脑了,随着业务的拓展,这个代产 商还要产惠普的电脑,我们就需要个单独的类来专产电脑,这就到了简单模式。 下我们来实现简单模式: 1. 创建抽象产品类 我们创建个电脑的抽象产品类,他有个抽象法于启动电脑: databaseldProviderElement(root.evalNode("databaseldProvider")); } //解析标签 mapperElement(root.evalNode("mappers")); 2. 创建具体产品类 接着我们创建各个品牌的电脑,他们都继承了他们的类Computer,并实现了类的start法: 3. 创建类 接下来创建个类,它提供了个静态法createComputer来产电脑。你只需要传你 想 产的电脑的品牌,它就会实例化相应品牌的电脑对象 客户端调类 客户端调类,传“hp”产出惠普电脑并调该电脑对象的start法: public abstract class Computer { /** *产品的抽象法,由具体的产品类去实现 */ public abstract void start(); } public class LenovoComputer extends Computer{ @Override public void start() { System.out.println("联想电脑启动"); } public class HpComputer extends Computer{ @Override public void start() { System.out.println("惠普电脑启动"); } } import org.junit.runner.Computer; public class ComputerFactory { public static Computer createComputer(String type){ Computer mComputer=null; switch (type) { case "lenovo": mComputer=new LenovoComputer(); break; case "hp": mComputer=new HpComputer(); break; } return mComputer; } } Mybatis 体现: Mybatis中执Sql语句、获取Mappers、管理事务的核接SqlSession的创建过程使到了模 式。 有个 SqlSessionFactory 来负责 SqlSession 的创建 SqlSessionFactory 可以看到,该Factory的openSession ()法重载了很多个,分别 持autoCommit、Executor、Transaction等参数的输,来构建核的SqlSession对象。 在DefaultSqlSessionFactory的默认实现,有个法可以看出怎么产出个产品: public class CreatComputer { public static void main(String[]args){ ComputerFactory.createComputer("hp").start(); } } private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,boolean autoCommit){ Transaction tx=null; try{ final Environment environment=configuration.getEnvironment(); final TransactionFactory transactionFactory= getTransactionFactoryFromEnvironment(environment); tx=transactionFactory.newTransaction(environment.getDataSource(),level,autoCo mmit); 这是个openSession调的底层法,该法先从configuration读取对应的环境配置,然后初始化 TransactionFactory 获得个 Transaction 对象,然后通过 Transaction 获取个 Executor 对象,最 后通过configuration、Executor、是否autoCommit三个参数构建了 SqlSession 11.3 代理模式 代理模式(Proxy Pattern):给某个对象提供个代理,并由代理对象控制对原对象的引。代理模式 的英叫做Proxy,它是种对象结构型模式,代理模式分为静态代理和动态代理,我们来介绍动态代 理 举例: 创建个抽象类,Person接,使其拥有个没有返回值的doSomething法。 创建个名为Bob的Person接的实现类,使其实现doSomething法 (3) 创建JDK动态代理类,使其实现InvocationHandler接。拥有个名为target的变量,并创建 getTa rget获取代理对象法 //根据参数创建制定类型的Executor final Executor executor=configuration.newExecutor(tx,execType); //返回的是 DefaultSqlSession return new DefaultSqlSession(configuration,executor,autoCommit); }catch(Exception e){ closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: "+ e,e); }finally{ ErrorContext.instance().reset(); } } /** * 抽象类 */ public interface Person { void doSomething(); } /** * 创建个名为Bob的的实现类 */ public class Bob implements Person { public void doSomething() { System.out.println("Bob doing something!"); } } 创建JDK动态代理测试类J DKDynamicTest Mybatis中实现: /** * JDK动态代理 * 需实现 InvocationHandler 接 */ public class JDKDynamicProxy implements InvocationHandler { //被代理的对象 Person target; // JDKDynamicProxy 构造函数 public JDKDynamicProxy(Person person) { this.target = person; } //获取代理对象 public Person getTarget() { return (Person) Proxy.newProxylnstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } //动态代理invoke法 public Person invoke(Object proxy, Method method, Object[] args) throws Throwable { //被代理法前执 System.out.println("JDKDynamicProxy do something before!"); //执被代理的法 Person result = (Person) method.invoke(target, args); //被代理法后执 System.out.println("JDKDynamicProxy do something after!"); return result; } /** * JDK动态代理测试 */ public class JDKDynamicTest { public static void main(String[] args) { System.out.println("不使代理类,调doSomething法。"); //不使代理类 Person person = new Bob(); // 调 doSomething 法 person.doSomething(); System.out.println("分割线-----------"); System.out.println("使代理类,调doSomething法。"); //获取代理类 Person proxyPerson = new JDKDynamicProxy(new Bob()).getTarget(); // 调 doSomething 法 proxyPerson.doSomething(); } } 代理模式可以认为是Mybatis的核使的模式,正是由于这个模式,我们只需要编写Mapper.java接 ,不需要实现,由Mybati s后台帮我们完成具体SQL的执。 当我们使Configuration的getMapper法时,会调mapperRegistry.getMapper法,该法 会调 mapperProxyFactory.newInstance(sqlSession)来成个具体的代理: 在这,先通过T newInstance(SqlSession sqlSession)法会得到个MapperProxy对象,然后调T newInstance(MapperProxy mapperProxy)成代理对象然后返回。查看MapperProxy的代码,可 以看到如下内容: public class MapperProxyFactory { private final Class mapperInterface; private final Map<method, mappermethod=""> methodCache = new ConcurrentHashMap<method, mappermethod="">(); public MapperProxyFactory(Class mapperInterface) { this.mapperInterface = mapperInterface; } public Class getMapperInterface() { return mapperInterface; } public Map<method, mappermethod=""> getMethodCache() { return methodCache; @SuppressWarnings("unchecked") protected T newInstance(MapperProxy mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } } public class MapperProxy implements InvocationHandler, Serializable { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); 常典型的,该MapperProxy类实现了InvocationHandler接,并且实现了该接的invoke法。通 过这种式,我们只需要编写Mapper.java接类,当真正执个Mapper接的时候,就会转发给 MapperProxy.invoke法,该法则会调后续的 sqlSession.cud>executor.execute>prepareStatement 等系列法,完成 SQL 的执和返回 加餐:Mybatis-Plus 1. Mybatis-Plus概念 1.1 Mybatis-Plus介绍 官: https://mybatis.plus/ 或 https://mp.baomidou.com/ Mybatis-Plus介绍 MyBatis-Plus(简称 MP)是个 MyBatis 的增强具,在 MyBatis 的基础上只做增强不做改变,为简 化开发、提 效率。 } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); } 愿景 我们的愿景是成为 MyBatis 最好的搭档,就像 魂罗 中的 1P、2P,基友搭配,效率翻倍。 1.2 特性 侵:只做增强不做改变,引它不会对现有程产影响,如丝般顺滑 损耗:启动即会动注基本 CURD,性能基本损耗,直接向对象操作 强的 CRUD 操作:内置通 Mapper、通 Service,仅仅通过少量配置即可实现单表部分 CRUD 操作,更有强的条件构造器,满各类使需求 持 Lambda 形式调:通过 Lambda 表达式,便的编写各类查询条件,需再担字段写错 持主键动成:持多达 4 种主键策略(内含分布式唯 ID 成器 - Sequence),可由配 置,完美解决主键问题 持 ActiveRecord 模式:持 ActiveRecord 形式调,实体类只需继承 Model 类即可进强 的 CRUD 操作 持定义全局通操作:持全局通法注( Write once, use anywhere ) 内置代码成器:采代码或者 Maven 插件可快速成 Mapper 、 Model 、 Service 、 Controller 层代码,持模板引擎,更有超多定义配置等您来使 内置分插件:基于 MyBatis 物理分,开发者需关具体操作,配置好插件之后,写分等 同于普通 List 查询 分插件持多种数据库:持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、 Postgre、SQLServer 等多种数据库 内置性能分析插件:可输出 Sql 语句以及其执时间,建议开发测试时启该功能,能快速揪出慢 查询 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可定义拦截规则,预防 误操作 1.3 架构 1.4 作者 Mybatis-Plus是由baomidou(苞)组织开发并且开源的,前该组织概有30左右。 码云地址:https://gitee.com/organizations/baomidou 2. Mybatis-Plus快速 2.1 安装 全新的 MyBatis-Plus 3.0 版本基于 JDK8,提供了 lambda 形式的调,所以安装集成 MP3.0 要求 如下: JDK 8+ Maven or Gradle Release Spring Boot Maven: Spring MVC Maven: com.baomidou mybatis-plus-boot-starter 3.4.0 com.baomidou mybatis-plus 3.4.0 对于Mybatis整合MP有常常有三种法,分别是Mybatis+MP、Spring+Mybatis+MP、Spring Boot+Mybatis+MP。 2.2 创建数据库以及表 创建User表,其表结构如下: 2.3 创建程 -- 创建测试表 DROP TABLE IF EXISTS tb_user; CREATE TABLE user ( id BIGINT(20) NOT NULL COMMENT '主键ID', name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名', age INT(11) NULL DEFAULT NULL COMMENT '年龄', email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱', PRIMARY KEY (id) ); -- 插测试数据 INSERT INTO user (id, name, age, email) VALUES (1, 'Jone', 18, 'test1@baomidou.com'), (2, 'Jack', 20, 'test2@baomidou.com'), (3, 'Tom', 28, 'test3@baomidou.com'), (4, 'Sandy', 21, 'test4@baomidou.com'), (5, 'Billie', 24, 'test5@baomidou.com'); 导依赖:  com.baomidou mybatis-plus 3.1.1  mysql mysql-connector-java 5.1.47  com.alibaba druid 1.0.11  org.projectlombok lombok 1.18.4 2.4 Mybatis + MP 下演示,通过纯Mybatis与Mybatis-Plus整合。 创建Module junit junit 4.12 org.slf4j slf4j-log4j12 1.6.4 org.apache.maven.plugins maven-compiler-plugin 1.8 1.8  lagou-mybatis-plus com.lagou.mp 1.0-SNAPSHOT 4.0.0 log4j.properties: Mybatis实现查询User 第步,编写mybatis-config.xml件: lagou-mybatis-plus-simple log4j.rootLogger=DEBUG,A1 log4j.appender.A1=org.apache.log4j.ConsoleAppender log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=[%t] [%c]-[%p] %m%n      第步,编写User实体对象:(这使lombok进了进化bean操作) 第三步,编写UserMapper接: 第四步,编写UserMapper.xml件: 第五步,编写TestMybatis测试例: @Data // getter setter @toString @NoArgsConstructor @AllArgsConstructor public class User { private Long id; private String name; private Integer age; private String email; } public interface UserMapper { List findAll(); }  select * from userpublic class MPTest { @Test public void test1() throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); 测试结果: 注:如果实体类名称和表名称不致,可以在实体类上添加注解@TableName("指定数据库表名") Mybatis+MP实现查询User 第步,将UserMapper继承BaseMapper,将拥有了BaseMapper中的所有法: 第步,使MP中的MybatisSqlSessionFactoryBuilder进程构建: SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List all = mapper.findAll(); for (User user : all) { System.out.println(user); } } } User(id=1, name=Jone, age=18, email=test1@baomidou.com) User(id=2, name=Jack, age=20, email=test2@baomidou.com) User(id=3, name=Tom, age=28, email=test3@baomidou.com) User(id=4, name=Sandy, age=21, email=test4@baomidou.com) User(id=5, name=Billie, age=24, email=test5@baomidou.com) import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.lagou.pojo.User; public interface UserMapper extends BaseMapper { } @Test public void test2() throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); //这使的是MP中的MybatisSqlSessionFactoryBuilder SqlSessionFactory sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); // 可以调BaseMapper中定义的法 List all = mapper.selectList(null); for (User user : all) { 测试: 注:如果实体类名称和表名称不致,可以在实体类上添加注解@TableName("指定数据库表名") 简单说明: 由于使了 MybatisSqlSessionFactoryBuilder进了构建,继承的BaseMapper中的法就载 到了 SqlSession中,所以就可以直接使相关的法; 如图 2.5 Spring + Mybatis + MP 引了Spring框架,数据源、构建等作就交给了Spring管理。 创建Module System.out.println(user); } } User(id=1, name=Jone, age=18, email=test1@baomidou.com) User(id=2, name=Jack, age=20, email=test2@baomidou.com) User(id=3, name=Tom, age=28, email=test3@baomidou.com) User(id=4, name=Sandy, age=21, email=test4@baomidou.com) User(id=5, name=Billie, age=24, email=test5@baomidou.com)  lagou-mybatis-plus 实现查询User 第步,编写jdbc.properties 第步,编写applicationContext.xml com.lagou.mp 1.0-SNAPSHOT 4.0.0 lagou-mybatis-plus-spring 5.1.6.RELEASE org.springframework spring-webmvc ${spring.version} org.springframework spring-jdbc ${spring.version} org.springframework spring-test ${spring.version} jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/mp?serverTimezone=GMT%2B8&useSSL=false jdbc.username=root jdbc.password=root  第三步,编写User对象以及UserMapper接: 第四步,编写测试例:     @Data @NoArgsConstructor @AllArgsConstructor public class User { private Long id; private String name; private Integer age; private String email; } public interface UserMapper extends BaseMapper { List findAll(); } @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:applicationContext.xml") 2.6 SpringBoot + Mybatis + MP 使SpringBoot将进步的简化MP的整合,需要注意的是,由于使SpringBoot需要继承parent,所 以需要重新创 建程,并不是创建Module。 创建程 public class TestSpringMP { @Autowired private UserMapper userMapper; @Test public void test2() throws IOException { List users = this.userMapper.selectList(null); for (User user : users) { System.out.println(user); } } 导依赖 org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-logging org.springframework.boot spring-boot-starter-test test log4j.properties: 编写application.properties  org.projectlombok lombok true  com.baomidou mybatis-plus-boot-starter 3.1.1 mysqlmysql-connector-java5.1.47org.slf4jslf4j-log4j12org.springframework.bootspring-boot-maven-plugin log4j.rootLogger=DEBUG,A1 log4j.appender.A1=org.apache.log4j.ConsoleAppender log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=[%t] [%c]-[%p] %m%n 编写pojo 编写mapper 编写启动类 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mp? useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=tr ue&useSSL=false spring.datasource.username=root spring.datasource.password=root @Data @NoArgsConstructor @AllArgsConstructor public class User { private Long id; private String name; private Integer age; private String email; } } public interface UserMapper extends BaseMapper { } package com.lagou.mp; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; @MapperScan("com.lagou.mp.mapper") //设置mapper接的扫描包 @SpringBootApplication public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } 编写测试例 测试: 3. 通CRUD 通过前的学习,我们了解到通过继承BaseMapper就可以获取到各种各样的单表操作,接下来我们将 详细讲解这些操作。 } package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelect() { List userList = userMapper.selectList(null); for (User user : userList) { System.out.println(user); } } } User(id=1, name=Jone, age=18, email=test1@baomidou.com) User(id=2, name=Jack, age=20, email=test2@baomidou.com) User(id=3, name=Tom, age=28, email=test3@baomidou.com) User(id=4, name=Sandy, age=21, email=test4@baomidou.com) User(id=5, name=Billie, age=24, email=test5@baomidou.com) 3.1 插操作 法定义 测试例 /** * 插条记录 * * @param entity 实体对象. */ int insert(T entity); @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testInsert(){ User user = new User(); user.setAge(18); user.setEmail("test@lagou.cn"); user.setName("慕"); 测试 可以看到,数据已经写到了数据库,但是,id的值不正确,我们期望的是数据库增,实际是MP 成了id的值写到了数据库。 如何设置id的成策略呢? MP持的id策略: //返回的result是受影响的数,并不是增后的id int result = userMapper.insert(user); System.out.println(result); System.out.println(user.getId()); } } 1 1318744682116739074 package com.baomidou.mybatisplus.annotation; import lombok.Getter; /** * 成ID类型枚举类 * * @author hubin * @since 2015-11-10 */ @Getter public enum IdType { /** * 数据库ID增 */ AUTO(0), /** * 该类型为未设置主键类型 修改User对象: */ NONE(1), /** * 户输ID *
//#mark 创建结果对象 private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { this.useConstructorMappings = false; // reset previous mapping result final List<Class<?>> constructorArgTypes = new ArrayList<Class<?>>(); final List<Object> constructorArgs = new ArrayList<Object>(); //#mark 创建返回的结果映射的真实对象 Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix); if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings(); for (ResultMapping propertyMapping : propertyMappings) { // 判断属性有没配置嵌套查询,如果有就创建代理对象 if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) { //#mark 创建延迟加载代理对象 resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); break; } } } this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result return resultObject; 默认采javassistProxy进代理对象的创建 JavasisstProxyFactory实现 } public class JavassistProxyFactory implements org.apache.ibatis.executor.loader.ProxyFactory { /** * 接实现 * @param target 标结果对象 * @param lazyLoader 延迟加载对象 * @param configuration 配置 * @param objectFactory 对象 * @param constructorArgTypes 构造参数类型 * @param constructorArgs 构造参数值 * @return */ @Override public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<? >> constructorArgTypes, List<Object> constructorArgs) { return EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); } //省略... /** * 代理对象实现,核逻辑执 */ private static class EnhancedResultObjectProxyImpl implements MethodHandler { /** * 创建代理对象 * @param type * @param callback * @param constructorArgTypes * @param constructorArgs * @return */ static Object crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { ProxyFactory enhancer = new ProxyFactory(); enhancer.setSuperclass(type); try { //通过获取对象法,判断是否存在该法 type.getDeclaredMethod(WRITE_REPLACE_METHOD); // ObjectOutputStream will call writeReplace of objects returned by writeReplace if (log.isDebugEnabled()) { log.debug(WRITE_REPLACE_METHOD + " method was found on bean " + type + ", make sure it returns this"); } } catch (NoSuchMethodException e) { //没找到该法,实现接 enhancer.setInterfaces(new Class[]{WriteReplaceInterface.class}); } catch (SecurityException e) { // nothing to do here } Object enhanced; Class<?>[] typesArray = constructorArgTypes.toArray(new Class[constructorArgTypes.size()]); Object[] valuesArray = constructorArgs.toArray(new Object[constructorArgs.size()]); try { //创建新的代理对象 enhanced = enhancer.create(typesArray, valuesArray); } catch (Exception e) { throw new ExecutorException("Error creating lazy proxy. Cause: " + e, e); } //设置代理执器 ((Proxy) enhanced).setHandler(callback); return enhanced; } /** * 代理对象执 * @param enhanced 原对象 * @param method 原对象法 * @param methodProxy 代理法 * @param args 法参数 * @return * @throws Throwable */ @Override public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable { final String methodName = method.getName(); try { synchronized (lazyLoader) { if (WRITE_REPLACE_METHOD.equals(methodName)) { //忽略暂未找到具体作 Object original; if (constructorArgTypes.isEmpty()) { original = objectFactory.create(type); } else { original = objectFactory.create(type, constructorArgTypes, constructorArgs); } PropertyCopier.copyBeanProperties(type, enhanced, original); if (lazyLoader.size() > 0) { return new JavassistSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs); } else { return original; } } else { //延迟加载数量于0 if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) { //aggressive 次加载性所有需要要延迟加载属性或者包含触发延迟加载法 if (aggressive || lazyLoadTriggerMethods.contains(methodName)) { log.debug("==> laze lod trigger method:" + methodName + ",proxy method:" + methodProxy.getName() + " class:" + enhanced.getClass()); //次全部加载 lazyLoader.loadAll(); } else if (PropertyNamer.isSetter(methodName)) { //判断是否为set法,set法不需要延迟加载 final String property = PropertyNamer.methodToProperty(methodName); lazyLoader.remove(property); } else if (PropertyNamer.isGetter(methodName)) { final String property = PropertyNamer.methodToProperty(methodName); if (lazyLoader.hasLoader(property)) { //延迟加载单个属性 lazyLoader.load(property); log.debug("load one :" + methodName); } } 注意事项 1. IDEA调试问题 当配置aggressiveLazyLoading=true,在使IDEA进调试的时候,如果断点打到 代理执逻辑当中,你会发现延迟加载的代码永远都不能进,总是会被提前执。 主要产的 原因在aggressiveLazyLoading,因为在调试的时候,IDEA的Debuger窗体中已经触发了延迟加载 对象的法。 第部分:设计模式 虽然我们都知道有3类23种设计模式,但是多停留在概念层,Mybatis源码中使了量的设计模 式,观察设计模式在其中的应,能够更深的理解设计模式 Mybati s少到了以下的设计模式的使: } } } return methodProxy.invoke(enhanced, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } } 模式 mybatis 体现 Builder 模式 例如SqlSessionFactoryBuilder、Environment;  法模式 例如SqlSessionFactory、TransactionFactory、LogFactory 单例模 式 例如 ErrorContext 和 LogFactory; 代理模 式 Mybatis实现的核,如MapperProxy、ConnectionLogger,的jdk的动态代理 还有executor.loader包使了 cglib或者javassist达到延迟加载的效果 组合模 式 例如SqlNode和各个类ChooseSqlNode等; 模板 法模式 例如 BaseExecutor 和 SimpleExecutor,还有 BaseTypeHandler 和所有的类例如 IntegerTypeHandler; 适配器 模式 例如Log的Mybatis接和它对jdbc、log4j等各种志框架的适配实现; 装饰者 模式 例如Cache包中的cache.decorators包中等各个装饰者的实现; 迭代器 模式 例如迭代器模式PropertyTokenizer; 接下来对Builder构建者模式、模式、代理模式进解读,先介绍模式身的知识,然后解读在 Mybatis中怎样应了该模式。 11.1 Builder构建者模式 Builder模式的定义是"将个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表 示。”,它属于创建类模式,般来说,如果个对象的构建较复杂,超出了构造函数所能包含的范 围,就可以使模式和Builder模式,相对于模式会产出个完整的产品,Builder应于更加 复杂的对象的构建,甚只会构建产品的个部分,直来说,就是使多个简单的对象步步构建 成个复杂的对象 例:使构建者设计模式来产computer 主要步骤: 1、将需要构建的标类分成多个部件(电脑可以分为主机、显示器、键盘、箱等部件); 2、 创建构建类; 3、 依次创建部件; 4、 将部件组装成标对象 1. 定义computer ComputerBuilder package com.lagou.dao; import org.apache.ibatis.binding.BindingException; import org.apache.ibatis.session.SqlSession; import java.util.Optional; public class Computer { private String displayer; private String mainUnit; private String mouse; private String keyboard; public String getDisplayer() { return displayer; } public void setDisplayer(String displayer) { this.displayer = displayer; } public String getMainUnit() { return mainUnit; } public void setMainUnit(String mainUnit) { this.mainUnit = mainUnit; } public String getMouse() { return mouse; } public void setMouse(String mouse) { this.mouse = mouse; } public String getKeyboard() { return keyboard; } public void setKeyboard(String keyboard) { this.keyboard = keyboard; } @Override public String toString() { return "Computer{" + "displayer='" + displayer + '\'' + ", mainUnit='" + mainUnit + '\'' + ", mouse='" + mouse + '\'' + ", keyboard='" + keyboard + '\'' + '}'; } public static class ComputerBuilder { 调 Mybatis中的体现 SqlSessionFactory 的构建过程: Mybatis的初始化作常复杂,不是只个构造函数就能搞定的。所以使了建造者模式,使了  量的Builder,进分层构造,核对象Configuration使了 XmlConfigBuilder来进构造 private ComputerBuilder target = new ComputerBuilder(); public Builder installDisplayer(String displayer) { target.setDisplayer(displayer); return this; } public Builder installMainUnit(String mainUnit) { target.setMainUnit(mainUnit); return this; } public Builder installMouse(String mouse) { target.setMouse(mouse); return this; } public Builder installKeybord(String keyboard) { target.setKeyboard(keyboard); return this; } public ComputerBuilder build() { return target; } } public static void main(String[]args){ ComputerBuilder computerBuilder=new ComputerBuilder(); computerBuilder.installDisplayer("显万器"); computerBuilder.installMainUnit("主机"); computerBuilder.installKeybord("键盘"); computerBuilder.installMouse("标"); Computer computer=computerBuilder.Builder(); System.out.println(computer); } 在Mybatis环境的初始化过程中,SqlSessionFactoryBuilder会调XMLConfigBuilder读取所有的 MybatisMapConfig.xml 和所有的 *Mapper.xml 件,构建 Mybatis 运的核对象 Configuration 对 象,然后将该Configuration对象作为参数构建个SqlSessionFactory对象。 private void parseConfiguration(XNode root) { try { //issue #117 read properties first //解析标签 propertiesElement(root.evalNode("properties")); // 解析 标签 Properties settings = settingsAsProperties(root.evalNode("settings")); //加载定义的VFS实现类 loadCustomVfs(settings); // 解析 标签 typeAliasesElement(root.evalNode("typeAliases")); //解析标签 pluginElement(root.evalNode("plugins")); // 解析 标签 objectFactoryElement(root.evaINode("obj ectFactory")); // 解析 标签 obj ectWrappe rFacto ryElement(root.evalNode("objectWrapperFactory")); // 解析 标签 reflectorFactoryElement(root.evalNode("reflectorFactory")); // 赋值 到 Configuration 属性 settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 // 解析 标签 environmentsElement(root.evalNode("environments")); // 解析 标签 其中 XMLConfigBuilder 在构建 Configuration 对象时,也会调 XMLMapperBuilder 于读取 *Mapper 件,XMLMapperBuilder会使XMLStatementBuilder来读取和build所有的SQL语句。 在这个过程中,有个相似的特点,就是这些Builder会读取件或者配置,然后做量的XpathParser 解析、配置或语法的解析、反射成对象、存结果缓存等步骤,这么多的作都不是个构造函数所 能包括的,因此量采了 Builder模式来解决 SqlSessionFactoryBuilder类根据不同的输参数来构建SqlSessionFactory这个对象 11.2 模式 在Mybatis中如SqlSessionFactory使的是模式,该没有那么复杂的逻辑,是个简单 模式。 简单模式(Simple Factory Pattern):称为静态法(Static Factory Method)模式,它属于创 建型模式。 在简单模式中,可以根据参数的不同返回不同类的实例。简单模式专定义个类来负责创建 其他类的实例,被创建的实例通常都具有共同的类 例:产电脑 假设有个电脑的代产商,它前已经可以代产联想电脑了,随着业务的拓展,这个代产 商还要产惠普的电脑,我们就需要个单独的类来专产电脑,这就到了简单模式。 下我们来实现简单模式: 1. 创建抽象产品类 我们创建个电脑的抽象产品类,他有个抽象法于启动电脑: databaseldProviderElement(root.evalNode("databaseldProvider")); } //解析标签 mapperElement(root.evalNode("mappers")); 2. 创建具体产品类 接着我们创建各个品牌的电脑,他们都继承了他们的类Computer,并实现了类的start法: 3. 创建类 接下来创建个类,它提供了个静态法createComputer来产电脑。你只需要传你 想 产的电脑的品牌,它就会实例化相应品牌的电脑对象 客户端调类 客户端调类,传“hp”产出惠普电脑并调该电脑对象的start法: public abstract class Computer { /** *产品的抽象法,由具体的产品类去实现 */ public abstract void start(); } public class LenovoComputer extends Computer{ @Override public void start() { System.out.println("联想电脑启动"); } public class HpComputer extends Computer{ @Override public void start() { System.out.println("惠普电脑启动"); } } import org.junit.runner.Computer; public class ComputerFactory { public static Computer createComputer(String type){ Computer mComputer=null; switch (type) { case "lenovo": mComputer=new LenovoComputer(); break; case "hp": mComputer=new HpComputer(); break; } return mComputer; } } Mybatis 体现: Mybatis中执Sql语句、获取Mappers、管理事务的核接SqlSession的创建过程使到了模 式。 有个 SqlSessionFactory 来负责 SqlSession 的创建 SqlSessionFactory 可以看到,该Factory的openSession ()法重载了很多个,分别 持autoCommit、Executor、Transaction等参数的输,来构建核的SqlSession对象。 在DefaultSqlSessionFactory的默认实现,有个法可以看出怎么产出个产品: public class CreatComputer { public static void main(String[]args){ ComputerFactory.createComputer("hp").start(); } } private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,boolean autoCommit){ Transaction tx=null; try{ final Environment environment=configuration.getEnvironment(); final TransactionFactory transactionFactory= getTransactionFactoryFromEnvironment(environment); tx=transactionFactory.newTransaction(environment.getDataSource(),level,autoCo mmit); 这是个openSession调的底层法,该法先从configuration读取对应的环境配置,然后初始化 TransactionFactory 获得个 Transaction 对象,然后通过 Transaction 获取个 Executor 对象,最 后通过configuration、Executor、是否autoCommit三个参数构建了 SqlSession 11.3 代理模式 代理模式(Proxy Pattern):给某个对象提供个代理,并由代理对象控制对原对象的引。代理模式 的英叫做Proxy,它是种对象结构型模式,代理模式分为静态代理和动态代理,我们来介绍动态代 理 举例: 创建个抽象类,Person接,使其拥有个没有返回值的doSomething法。 创建个名为Bob的Person接的实现类,使其实现doSomething法 (3) 创建JDK动态代理类,使其实现InvocationHandler接。拥有个名为target的变量,并创建 getTa rget获取代理对象法 //根据参数创建制定类型的Executor final Executor executor=configuration.newExecutor(tx,execType); //返回的是 DefaultSqlSession return new DefaultSqlSession(configuration,executor,autoCommit); }catch(Exception e){ closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: "+ e,e); }finally{ ErrorContext.instance().reset(); } } /** * 抽象类 */ public interface Person { void doSomething(); } /** * 创建个名为Bob的的实现类 */ public class Bob implements Person { public void doSomething() { System.out.println("Bob doing something!"); } } 创建JDK动态代理测试类J DKDynamicTest Mybatis中实现: /** * JDK动态代理 * 需实现 InvocationHandler 接 */ public class JDKDynamicProxy implements InvocationHandler { //被代理的对象 Person target; // JDKDynamicProxy 构造函数 public JDKDynamicProxy(Person person) { this.target = person; } //获取代理对象 public Person getTarget() { return (Person) Proxy.newProxylnstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } //动态代理invoke法 public Person invoke(Object proxy, Method method, Object[] args) throws Throwable { //被代理法前执 System.out.println("JDKDynamicProxy do something before!"); //执被代理的法 Person result = (Person) method.invoke(target, args); //被代理法后执 System.out.println("JDKDynamicProxy do something after!"); return result; } /** * JDK动态代理测试 */ public class JDKDynamicTest { public static void main(String[] args) { System.out.println("不使代理类,调doSomething法。"); //不使代理类 Person person = new Bob(); // 调 doSomething 法 person.doSomething(); System.out.println("分割线-----------"); System.out.println("使代理类,调doSomething法。"); //获取代理类 Person proxyPerson = new JDKDynamicProxy(new Bob()).getTarget(); // 调 doSomething 法 proxyPerson.doSomething(); } } 代理模式可以认为是Mybatis的核使的模式,正是由于这个模式,我们只需要编写Mapper.java接 ,不需要实现,由Mybati s后台帮我们完成具体SQL的执。 当我们使Configuration的getMapper法时,会调mapperRegistry.getMapper法,该法 会调 mapperProxyFactory.newInstance(sqlSession)来成个具体的代理: 在这,先通过T newInstance(SqlSession sqlSession)法会得到个MapperProxy对象,然后调T newInstance(MapperProxy mapperProxy)成代理对象然后返回。查看MapperProxy的代码,可 以看到如下内容: public class MapperProxyFactory { private final Class mapperInterface; private final Map<method, mappermethod=""> methodCache = new ConcurrentHashMap<method, mappermethod="">(); public MapperProxyFactory(Class mapperInterface) { this.mapperInterface = mapperInterface; } public Class getMapperInterface() { return mapperInterface; } public Map<method, mappermethod=""> getMethodCache() { return methodCache; @SuppressWarnings("unchecked") protected T newInstance(MapperProxy mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } } public class MapperProxy implements InvocationHandler, Serializable { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); 常典型的,该MapperProxy类实现了InvocationHandler接,并且实现了该接的invoke法。通 过这种式,我们只需要编写Mapper.java接类,当真正执个Mapper接的时候,就会转发给 MapperProxy.invoke法,该法则会调后续的 sqlSession.cud>executor.execute>prepareStatement 等系列法,完成 SQL 的执和返回 加餐:Mybatis-Plus 1. Mybatis-Plus概念 1.1 Mybatis-Plus介绍 官: https://mybatis.plus/ 或 https://mp.baomidou.com/ Mybatis-Plus介绍 MyBatis-Plus(简称 MP)是个 MyBatis 的增强具,在 MyBatis 的基础上只做增强不做改变,为简 化开发、提 效率。 } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); } 愿景 我们的愿景是成为 MyBatis 最好的搭档,就像 魂罗 中的 1P、2P,基友搭配,效率翻倍。 1.2 特性 侵:只做增强不做改变,引它不会对现有程产影响,如丝般顺滑 损耗:启动即会动注基本 CURD,性能基本损耗,直接向对象操作 强的 CRUD 操作:内置通 Mapper、通 Service,仅仅通过少量配置即可实现单表部分 CRUD 操作,更有强的条件构造器,满各类使需求 持 Lambda 形式调:通过 Lambda 表达式,便的编写各类查询条件,需再担字段写错 持主键动成:持多达 4 种主键策略(内含分布式唯 ID 成器 - Sequence),可由配 置,完美解决主键问题 持 ActiveRecord 模式:持 ActiveRecord 形式调,实体类只需继承 Model 类即可进强 的 CRUD 操作 持定义全局通操作:持全局通法注( Write once, use anywhere ) 内置代码成器:采代码或者 Maven 插件可快速成 Mapper 、 Model 、 Service 、 Controller 层代码,持模板引擎,更有超多定义配置等您来使 内置分插件:基于 MyBatis 物理分,开发者需关具体操作,配置好插件之后,写分等 同于普通 List 查询 分插件持多种数据库:持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、 Postgre、SQLServer 等多种数据库 内置性能分析插件:可输出 Sql 语句以及其执时间,建议开发测试时启该功能,能快速揪出慢 查询 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可定义拦截规则,预防 误操作 1.3 架构 1.4 作者 Mybatis-Plus是由baomidou(苞)组织开发并且开源的,前该组织概有30左右。 码云地址:https://gitee.com/organizations/baomidou 2. Mybatis-Plus快速 2.1 安装 全新的 MyBatis-Plus 3.0 版本基于 JDK8,提供了 lambda 形式的调,所以安装集成 MP3.0 要求 如下: JDK 8+ Maven or Gradle Release Spring Boot Maven: Spring MVC Maven: com.baomidou mybatis-plus-boot-starter 3.4.0 com.baomidou mybatis-plus 3.4.0 对于Mybatis整合MP有常常有三种法,分别是Mybatis+MP、Spring+Mybatis+MP、Spring Boot+Mybatis+MP。 2.2 创建数据库以及表 创建User表,其表结构如下: 2.3 创建程 -- 创建测试表 DROP TABLE IF EXISTS tb_user; CREATE TABLE user ( id BIGINT(20) NOT NULL COMMENT '主键ID', name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名', age INT(11) NULL DEFAULT NULL COMMENT '年龄', email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱', PRIMARY KEY (id) ); -- 插测试数据 INSERT INTO user (id, name, age, email) VALUES (1, 'Jone', 18, 'test1@baomidou.com'), (2, 'Jack', 20, 'test2@baomidou.com'), (3, 'Tom', 28, 'test3@baomidou.com'), (4, 'Sandy', 21, 'test4@baomidou.com'), (5, 'Billie', 24, 'test5@baomidou.com'); 导依赖:  com.baomidou mybatis-plus 3.1.1  mysql mysql-connector-java 5.1.47  com.alibaba druid 1.0.11  org.projectlombok lombok 1.18.4 2.4 Mybatis + MP 下演示,通过纯Mybatis与Mybatis-Plus整合。 创建Module junit junit 4.12 org.slf4j slf4j-log4j12 1.6.4 org.apache.maven.plugins maven-compiler-plugin 1.8 1.8  lagou-mybatis-plus com.lagou.mp 1.0-SNAPSHOT 4.0.0 log4j.properties: Mybatis实现查询User 第步,编写mybatis-config.xml件: lagou-mybatis-plus-simple log4j.rootLogger=DEBUG,A1 log4j.appender.A1=org.apache.log4j.ConsoleAppender log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=[%t] [%c]-[%p] %m%n      第步,编写User实体对象:(这使lombok进了进化bean操作) 第三步,编写UserMapper接: 第四步,编写UserMapper.xml件: 第五步,编写TestMybatis测试例: @Data // getter setter @toString @NoArgsConstructor @AllArgsConstructor public class User { private Long id; private String name; private Integer age; private String email; } public interface UserMapper { List findAll(); }  select * from userpublic class MPTest { @Test public void test1() throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); 测试结果: 注:如果实体类名称和表名称不致,可以在实体类上添加注解@TableName("指定数据库表名") Mybatis+MP实现查询User 第步,将UserMapper继承BaseMapper,将拥有了BaseMapper中的所有法: 第步,使MP中的MybatisSqlSessionFactoryBuilder进程构建: SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List all = mapper.findAll(); for (User user : all) { System.out.println(user); } } } User(id=1, name=Jone, age=18, email=test1@baomidou.com) User(id=2, name=Jack, age=20, email=test2@baomidou.com) User(id=3, name=Tom, age=28, email=test3@baomidou.com) User(id=4, name=Sandy, age=21, email=test4@baomidou.com) User(id=5, name=Billie, age=24, email=test5@baomidou.com) import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.lagou.pojo.User; public interface UserMapper extends BaseMapper { } @Test public void test2() throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); //这使的是MP中的MybatisSqlSessionFactoryBuilder SqlSessionFactory sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); // 可以调BaseMapper中定义的法 List all = mapper.selectList(null); for (User user : all) { 测试: 注:如果实体类名称和表名称不致,可以在实体类上添加注解@TableName("指定数据库表名") 简单说明: 由于使了 MybatisSqlSessionFactoryBuilder进了构建,继承的BaseMapper中的法就载 到了 SqlSession中,所以就可以直接使相关的法; 如图 2.5 Spring + Mybatis + MP 引了Spring框架,数据源、构建等作就交给了Spring管理。 创建Module System.out.println(user); } } User(id=1, name=Jone, age=18, email=test1@baomidou.com) User(id=2, name=Jack, age=20, email=test2@baomidou.com) User(id=3, name=Tom, age=28, email=test3@baomidou.com) User(id=4, name=Sandy, age=21, email=test4@baomidou.com) User(id=5, name=Billie, age=24, email=test5@baomidou.com)  lagou-mybatis-plus 实现查询User 第步,编写jdbc.properties 第步,编写applicationContext.xml com.lagou.mp 1.0-SNAPSHOT 4.0.0 lagou-mybatis-plus-spring 5.1.6.RELEASE org.springframework spring-webmvc ${spring.version} org.springframework spring-jdbc ${spring.version} org.springframework spring-test ${spring.version} jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/mp?serverTimezone=GMT%2B8&useSSL=false jdbc.username=root jdbc.password=root  第三步,编写User对象以及UserMapper接: 第四步,编写测试例:     @Data @NoArgsConstructor @AllArgsConstructor public class User { private Long id; private String name; private Integer age; private String email; } public interface UserMapper extends BaseMapper { List findAll(); } @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:applicationContext.xml") 2.6 SpringBoot + Mybatis + MP 使SpringBoot将进步的简化MP的整合,需要注意的是,由于使SpringBoot需要继承parent,所 以需要重新创 建程,并不是创建Module。 创建程 public class TestSpringMP { @Autowired private UserMapper userMapper; @Test public void test2() throws IOException { List users = this.userMapper.selectList(null); for (User user : users) { System.out.println(user); } } 导依赖 org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-logging org.springframework.boot spring-boot-starter-test test log4j.properties: 编写application.properties  org.projectlombok lombok true  com.baomidou mybatis-plus-boot-starter 3.1.1 mysqlmysql-connector-java5.1.47org.slf4jslf4j-log4j12org.springframework.bootspring-boot-maven-plugin log4j.rootLogger=DEBUG,A1 log4j.appender.A1=org.apache.log4j.ConsoleAppender log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=[%t] [%c]-[%p] %m%n 编写pojo 编写mapper 编写启动类 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mp? useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=tr ue&useSSL=false spring.datasource.username=root spring.datasource.password=root @Data @NoArgsConstructor @AllArgsConstructor public class User { private Long id; private String name; private Integer age; private String email; } } public interface UserMapper extends BaseMapper { } package com.lagou.mp; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; @MapperScan("com.lagou.mp.mapper") //设置mapper接的扫描包 @SpringBootApplication public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } 编写测试例 测试: 3. 通CRUD 通过前的学习,我们了解到通过继承BaseMapper就可以获取到各种各样的单表操作,接下来我们将 详细讲解这些操作。 } package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelect() { List userList = userMapper.selectList(null); for (User user : userList) { System.out.println(user); } } } User(id=1, name=Jone, age=18, email=test1@baomidou.com) User(id=2, name=Jack, age=20, email=test2@baomidou.com) User(id=3, name=Tom, age=28, email=test3@baomidou.com) User(id=4, name=Sandy, age=21, email=test4@baomidou.com) User(id=5, name=Billie, age=24, email=test5@baomidou.com) 3.1 插操作 法定义 测试例 /** * 插条记录 * * @param entity 实体对象. */ int insert(T entity); @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testInsert(){ User user = new User(); user.setAge(18); user.setEmail("test@lagou.cn"); user.setName("慕"); 测试 可以看到,数据已经写到了数据库,但是,id的值不正确,我们期望的是数据库增,实际是MP 成了id的值写到了数据库。 如何设置id的成策略呢? MP持的id策略: //返回的result是受影响的数,并不是增后的id int result = userMapper.insert(user); System.out.println(result); System.out.println(user.getId()); } } 1 1318744682116739074 package com.baomidou.mybatisplus.annotation; import lombok.Getter; /** * 成ID类型枚举类 * * @author hubin * @since 2015-11-10 */ @Getter public enum IdType { /** * 数据库ID增 */ AUTO(0), /** * 该类型为未设置主键类型 修改User对象: */ NONE(1), /** * 户输ID *

该类型可以通过注册动填充插件进填充

*/ INPUT(2), /* 以下3种类型、只有当插对象ID 为空,才动填充。 */ /** * 全局唯ID (idWorker) */ ID_WORKER(3), /** * 全局唯ID (UUID) */ UUID(4), /** * 字符串全局唯ID (idWorker 的字符串表示) */ ID_WORKER_STR(5); private final int key; IdType(int key) { this.key = key; } } package com.lagou.mp.pojo; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor @TableName("tb_user") public class User { @TableId(type = IdType.AUTO) //指定id类型为增 private Long id; 数据插成功: @TableField 在MP中通过@TableField注解可以指定字段的些属性,常常解决的问题有2个: 1、对象中的属性名和字段名不致的问题(驼峰) 2、对象中的属性字段在表中不存在的问题 使: 其他法,如字段不加查询字段: private String userName; private String password; private String name; private Integer age; private String email; } 效果: 3.2 更新操作 在MP中,更新操作有2种,种是根据id更新,另种是根据条件更新。 根据id更新 法定义: 测试: /** * 根据 ID 修改 * * @param entity 实体对象 */ int updateById(@Param(Constants.ENTITY) T entity); @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { 结果: 根据条件更新 法定义: 测试例: @Autowired private UserMapper userMapper; @Test public void testUpdateById() { User user = new User(); user.setId(6L); //主键 user.setAge(21); //更新的字段 //根据id更新,更新不为null的字段 this.userMapper.updateById(user); } } /** * 根据 whereEntity 条件,更新记录 * * @param entity 实体对象 (set 条件值,可以为 null) * @param updateWrapper 实体对象封装操作类(可以为 null,的 entity 于成 where 语句) */ int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper updateWrapper); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 或者,通过UpdateWrapper进更新: 测试结果: import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import net.minidev.json.writer.UpdaterMapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testUpdate() { User user = new User(); user.setAge(22); //更新的字段 //更新的条件 QueryWrapper wrapper = new QueryWrapper<>(); wrapper.eq("id", 6); //执更新操作 int result = this.userMapper.update(user, wrapper); System.out.println("result = " + result); } } @Test public void testUpdate() { //更新的条件以及字段 UpdateWrapper wrapper = new UpdateWrapper<>(); wrapper.eq("id", 6).set("age", 23); //执更新操作 int result = this.userMapper.update(null, wrapper); System.out.println("result = " + result); } 均可达到更新的效果。 关于wrapper更多的法后会详细讲解。 3.3 删除操作 deleteById 法定义: 测试例: [main] [com.lagou.mp.mapper.UserMapper.update]-[DEBUG] ==> Preparing: UPDATE tb_user SET age=? WHERE id = ? [main] [com.lagou.mp.mapper.UserMapper.update]-[DEBUG] ==> Parameters: 23(Integer), 6(Integer) [main] [com.lagou.mp.mapper.UserMapper.update]-[DEBUG] <== Updates: 1 /** * 根据 ID 删除 * * @param id 主键ID */ int deleteById(Serializable id); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testDeleteById() { //执删除操作 int result = this.userMapper.deleteById(6L); System.out.println("result = " + result); } 结果: 数据被删除。 deleteByMap 法定义: 测试例: } [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] ==> Preparing: DELETE FROM tb_user WHERE id=? [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] ==> Parameters: 6(Long) [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] <== Updates: 1 /** * 根据 columnMap 条件,删除记录 * * @param columnMap 表字段 map 对象 */ int deleteByMap(@Param(Constants.COLUMN_MAP) Map<string, object=""> columnMap); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.HashMap; import java.util.Map; @RunWith(SpringRunner.class) 结果: delete 法定义: 测试例: @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testDeleteByMap() { Map<string, object=""> columnMap = new HashMap<>(); columnMap.put("age",21); columnMap.put("name","慕"); //将columnMap中的元素设置为删除的条件,多个之间为and关系 int result = this.userMapper.deleteByMap(columnMap); System.out.println("result = " + result); } } [main] [com.lagou.mp.mapper.UserMapper.deleteByMap]-[DEBUG] ==> Preparing: DELETE FROM tb_user WHERE name = ? AND age = ? [main] [com.lagou.mp.mapper.UserMapper.deleteByMap]-[DEBUG] ==> Parameters:  慕(String), 21(Integer) [main] [com.lagou.mp.mapper.UserMapper.deleteByMap]-[DEBUG] <== Updates: 0 /** * 根据 entity 条件,删除记录 * * @param wrapper 实体对象封装操作类(可以为 null) */ int delete(@Param(Constants.WRAPPER) Wrapper wrapper); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; 结果: 3.3.4、deleteBatchIds 法定义: import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.HashMap; import java.util.Map; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testDeleteByMap() { User user = new User(); user.setAge(20); user.setName("慕"); //将实体对象进包装,包装为操作条件 QueryWrapper wrapper = new QueryWrapper<>(user); int result = this.userMapper.delete(wrapper); System.out.println("result = " + result); } } [main] [com.lagou.mp.mapper.UserMapper.delete]-[DEBUG] ==> Preparing: DELETE FROM tb_user WHERE name=? AND age=? [main] [com.lagou.mp.mapper.UserMapper.delete]-[DEBUG] ==> Parameters: 慕 (String), 20(Integer) [main] [com.lagou.mp.mapper.UserMapper.delete]-[DEBUG] <== Updates: 0 测试例: 结果: /** * 删除(根据ID 批量删除) * * @param idList 主键ID列表(不能为 null 以及 empty) */ int deleteBatchIds(@Param(Constants.COLLECTION) Collection idList); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.Arrays; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testDeleteByMap() { //根据id集合批量删除 int result = this.userMapper.deleteBatchIds(Arrays.asList(1L,10L,20L)); System.out.println("result = " + result); } } [main] [com.lagou.mp.mapper.UserMapper.deleteBatchIds]-[DEBUG] ==> Preparing: DELETE FROM tb_user WHERE id IN ( ? , ? , ? ) [main] [com.lagou.mp.mapper.UserMapper.deleteBatchIds]-[DEBUG] ==> Parameters: 1(Long), 10(Long), 20(Long) [main] [com.lagou.mp.mapper.UserMapper.deleteBatchIds]-[DEBUG] <== Updates: 1 3.4 查询操作 MP提供了多种查询操作,包括根据id查询、批量查询、查询单条数据、查询列表、分查询等操作。 3.4.1、selectById 法定义: 测试例: 结果: 3.4.2、selectBatchIds /** * 根据 ID 查询 * * @param id 主键ID */ T selectById(Serializable id); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelectById() { //根据id查询数据 User user = this.userMapper.selectById(2L); System.out.println("result = " + user); } } result = User(id=2, name=Jack, age=20, email=test2@baomidou.com) 法定义: 测试例: 结果: /** * 查询(根据ID 批量查询) * * @param idList 主键ID列表(不能为 null 以及 empty) */ List selectBatchIds(@Param(Constants.COLLECTION) Collection idList); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.Arrays; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelectBatchIds() { //根据id集合批量查询 List users = this.userMapper.selectBatchIds(Arrays.asList(2L, 3L, 10L)); for (User user : users) { System.out.println(user); } } } User(id=2, name=Jack, age=20, email=test2@baomidou.com) User(id=3, name=Tom, age=28, email=test3@baomidou.com) 3.4.3、selectOne 法定义: 测试例: 结果: 3.4.4、selectCount /** * 根据 entity 条件,查询条记录 * * @param queryWrapper 实体对象封装操作类(可以为 null) */ T selectOne(@Param(Constants.WRAPPER) Wrapper queryWrapper); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelectOne() { QueryWrapper wrapper = new QueryWrapper(); wrapper.eq("name", "jack"); //根据条件查询条数据,如果结果超过条会报错 User user = this.userMapper.selectOne(wrapper); System.out.println(user); } } User(id=2, name=Jack, age=20, email=test2@baomidou.com) 法定义: 测试例: 结果: 3.4.5、selectList 法定义: /** * 根据 Wrapper 条件,查询总记录数 * * @param queryWrapper 实体对象封装操作类(可以为 null) */ Integer selectCount(@Param(Constants.WRAPPER) Wrapper queryWrapper); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelectCount() { QueryWrapper wrapper = new QueryWrapper(); wrapper.gt("age", 23); //年龄于23岁 //根据条件查询数据条数 Integer count = this.userMapper.selectCount(wrapper); System.out.println("count = " + count); } } count = 2 测试例: 结果: /** * 根据 entity 条件,查询全部记录 * * @param queryWrapper 实体对象封装操作类(可以为 null) */ List selectList(@Param(Constants.WRAPPER) Wrapper queryWrapper); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelectList() { QueryWrapper wrapper = new QueryWrapper(); wrapper.gt("age", 23); //年龄于23岁 //根据条件查询数据 List users = this.userMapper.selectList(wrapper); for (User user : users) { System.out.println("user = " + user); } } } user = User(id=3, name=Tom, age=28, email=test3@baomidou.com) user = User(id=5, name=Billie, age=24, email=test5@baomidou.com) 3.4.6、selectPage 法定义: 配置分插件: 测试例: /** * 根据 entity 条件,查询全部记录(并翻) * * @param page 分查询条件(可以为 RowBounds.DEFAULT) * @param queryWrapper 实体对象封装操作类(可以为 null) */ IPage selectPage(IPage page, @Param(Constants.WRAPPER) Wrapper queryWrapper); package com.lagou.mp; 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(); } } package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; 结果: 3.5 SQL注的原理 前我们已经知道,MP在启动后会将BaseMapper中的系列的法注册到meppedStatements中, 那么究竟是如何注的呢?流程是怎么样的?下我们将起来分析下。 在MP中,ISqlInjector负责SQL的注作,它是个接,AbstractSqlInjector是它的实现类,实现 关系如下: import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelectPage() { QueryWrapper wrapper = new QueryWrapper(); wrapper.gt("age", 20); //年龄于20岁 Page page = new Page<>(1,1); //根据条件查询数据 IPage iPage = this.userMapper.selectPage(page, wrapper); System.out.println("数据总条数:" + iPage.getTotal()); System.out.println("总数:" + iPage.getPages()); List users = iPage.getRecords(); for (User user : users) { System.out.println("user = " + user); } } } 数据总条数:4 总数:4 user = User(id=3, name=Tom, age=28, email=test3@baomidou.com) 在AbstractSqlInjector中,主要是由inspectInject()法进注的,如下: 在实现法中, methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo)); 是关键,循环遍历法,进注。 最终调抽象法injectMappedStatement进真正的注: @Override public void inspectInject(MapperBuilderAssistant builderAssistant, Class mapperClass) { Class modelClass = extractModelClass(mapperClass); if (modelClass != null) { String className = mapperClass.toString(); Set mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration()); if (!mapperRegistryCache.contains(className)) { List methodList = this.getMethodList(); if (CollectionUtils.isNotEmpty(methodList)) { TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass); // 循环注定义法 methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo)); } else { logger.debug(mapperClass.toString() + ", No effective injection method was found."); } mapperRegistryCache.add(className); } } } 查看该法的实现: 以SelectById为例查看: /** * 注定义 MappedStatement * * @param mapperClass mapper 接 * @param modelClass mapper 泛型 * @param tableInfo 数据库表反射信息 * @return MappedStatement */ public abstract MappedStatement injectMappedStatement(Class mapperClass, Class modelClass, TableInfo tableInfo); 可以看到,成了SqlSource对象,再将SQL通过addSelectMappedStatement法添加到 meppedStatements中。 4. 配置 在MP中有量的配置,其中有部分是Mybatis原的配置,另部分是MP的配置,详情:https://m ybatis.plus/config/ 下我们对常的配置做讲解。 4.1、基本配置 4.1.1、configLocation MyBatis 配置件位置,如果有单独的 MyBatis 配置,请将其路径配置到 configLocation 中。 MyBatis Configuration 的具体内容请参考MyBatis 官档 Spring Boot: public class SelectById extends AbstractMethod { @Override public MappedStatement injectMappedStatement(Class mapperClass, Class modelClass, TableInfo tableInfo) { SqlMethod sqlMethod = SqlMethod.LOGIC_SELECT_BY_ID; SqlSource sqlSource = new RawSqlSource(configuration, String.format(sqlMethod.getSql(), sqlSelectColumns(tableInfo, false), tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getKeyProperty(), tableInfo.getLogicDeleteSql(true, false)), Object.class); return this.addSelectMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource, modelClass, tableInfo); } } mybatis-plus.config-location = classpath:mybatis-config.xml Spring MVC: 4.1.2、mapperLocations MyBatis Mapper 所对应的 XML 件位置,如果您在 Mapper 中有定义法(XML 中有定义实 现),需要进该配置,告诉 Mapper 所对应的 XML 件位置。 Spring Boot: Spring MVC: Maven 多模块项的扫描路径需以 classpath*: classpath*: 开头 (即加载多个 jar 包下的 XML 件) 测试: UserMapper.xml: mybatis-plus.mapper-locations = classpath*:mybatis/*.xml select * from tb_user where id = #{id}测试例: 运结果: 4.1.3、typeAliasesPackage package com.lagou.mp.mapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.mapper.BaseMapper; public interface UserMapper extends BaseMapper { User findById(Long id); } package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelectPage() { User user = this.userMapper.findById(2L); System.out.println(user); } } MyBaits 别名包扫描路径,通过该属性可以给包中的类注册别名,注册后在 Mapper 对应的 XML 件 中可以直接使类名,不使全限定的类名(即 XML 中调的时候不包含包名)。 Spring Boot: Spring MVC: 4.2、进阶配置 本部分(Configuration)的配置都为 MyBatis 原持的配置,这意味着您可以通过 MyBatis XML 配置件的形式进配置。 4.2.1、mapUnderscoreToCamelCase 类型: boolean 默认值: true 是否开启动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN(下划线命名) 到 经典 Java 属性名 aColumn(驼峰命名) 的类似映射。 注意: 此属性在 MyBatis 中原默认值为 false,在 MyBatis-Plus 中,此属性也将于成最终的 SQL 的 select body 如果您的数据库命名符合规则需使 @TableField 注解指定数据库字段名 示例(SpringBoot): 4.2.2、cacheEnabled 类型: boolean 默认值: true 全局地开启或关闭配置件中的所有映射器已经配置的任何缓存,默认为 true。 示例: mybatis-plus.type-aliases-package = com.lagou.mp.pojo #关闭动驼峰映射,该参数不能和mybatis-plus.config-location同时存在 mybatis-plus.configuration.map-underscore-to-camel-case=false 4.3、DB 策略配置 4.3.1、idType 类型: com.baomidou.mybatisplus.annotation.IdType 默认值: ID_WORKER 全局默认主键类型,设置后,即可省略实体对象中的@TableId(type = IdType.AUTO)配置。 示例: SpringBoot: SpringMVC: 4.3.2、tablePrefix 类型: String 默认值: null 表名前缀,全局配置后可省略@TableName()配置。 SpringBoot: SpringMVC: mybatis-plus.configuration.cache-enabled=false mybatis-plus.global-config.db-config.id-type=auto  mybatis-plus.global-config.db-config.table-prefix=tb_ 5. 条件构造器 在MP中,Wrapper接的实现类关系如下: 可以看到,AbstractWrapper和AbstractChainWrapper是重点实现,接下来我们重点学习 AbstractWrapper以及其类。 说明: QueryWrapper(LambdaQueryWrapper) 和 UpdateWrapper(LambdaUpdateWrapper) 的类 于成 sql 的 where 条件, entity 属性也于成 sql 的 where 条件 注意: entity 成的 where 条件与 使各个 api 成的 where 条件没有任何关联为 官档地址:https://mybatis.plus/guide/wrapper.html 5.1、allEq 5.1.1、说明 全部eq(或个别isNull) 个别参数说明: params : key 为数据库字段名, value 为字段值 null2IsNull : 为 true 则在 map 的 value 为 null 时调 isNull 法,为 false 时则忽 略 value 为 null 的 例1: allEq({id:1,name:"王",age:null}) ---> id = 1 and name = '王' and age is null 例2: allEq({id:1,name:"王",age:null}, false) ---> id = 1 and name = '王' 个别参数说明: filter : 过滤函数,是否允许字段传对条件中 params 与 null2IsNull : 同上 例1: allEq((k,v) -> k.indexOf("a") > 0, {id:1,name:"王",age:null}) --- > name = '王' and age is null 例2: allEq((k,v) -> k.indexOf("a") > 0, {id:1,name:"王",age:null}, false) ---> name = '王' 5.1.2、测试例 allEq(Map<r, v=""> params) allEq(Map<r, v=""> params, boolean null2IsNull) allEq(boolean condition, Map<r, v=""> params, boolean null2IsNull) allEq(BiPredicate<r, v=""> filter, Map<r, v=""> params) allEq(BiPredicate<r, v=""> filter, Map<r, v=""> params, boolean null2IsNull) allEq(boolean condition, BiPredicate<r, v=""> filter, Map<r, v=""> params, boolean null2IsNull) package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.HashMap; import java.util.List; import java.util.Map; 5.2、基本较操作 eq 等于 = ne 不等于 <> gt 于 > ge 于等于 >= lt 于 < le @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testWrapper() { QueryWrapper wrapper = new QueryWrapper<>(); //设置条件 Map<string,object> params = new HashMap<>(); params.put("name", "jack"); params.put("age", "20"); // wrapper.allEq(params);//SELECT * FROM tb_user WHERE password IS NULL AND name = ? AND age = ? // wrapper.allEq(params,false); //SELECT * FROM tb_user WHERE name = ? AND age = ? // wrapper.allEq((k, v) -> (k.equals("name") || k.equals("age")) ,params);//SELECT * FROM tb_user WHERE name = ? AND age = ? List users = this.userMapper.selectList(wrapper); for (User user : users) { System.out.println(user); } } } 于等于 <= between BETWEEN 值1 AND 值2 notBetween NOT BETWEEN 值1 AND 值2 in 字段 IN (value.get(0), value.get(1), ...) notIn 字段 NOT IN (v0, v1, ...) 测试例: package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; QueryWrapper wrapper = new QueryWrapper<>(); //SELECT id,name,age,email FROM tb_user WHERE password = ? AND age >= ? AND name IN (?,?,?) wrapper.eq("email", "test2@baomidou.com") .ge("age", 20) .in("name", "jack", "jone", "tom"); List users = this.userMapper.selectList(wrapper); for (User user : users) { System.out.println(user); } } } 5.3、模糊查询 like LIKE '%值%' 例: like("name", "王") ---> name like '%王%' notLike NOT LIKE '%值%' 例: notLike("name", "王") ---> name not like '%王%' likeLeft LIKE '%值' 例: likeLeft("name", "王") ---> name like '%王' likeRight LIKE '值%' 例: likeRight("name", "王") ---> name like '王%' 测试例: package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testWrapper() { QueryWrapper wrapper = new QueryWrapper<>(); //SELECT id,user_name,password,name,age,email FROM tb_user WHERE name LIKE ? //Parameters: %%(String) wrapper.like("name", ""); List users = this.userMapper.selectList(wrapper); 5.4、排序 orderBy 排序:ORDER BY 字段, ... 例: orderBy(true, true, "id", "name") ---> order by id ASC,name ASC orderByAsc 排序:ORDER BY 字段, ... ASC 例: orderByAsc("id", "name") ---> order by id ASC,name ASC orderByDesc 排序:ORDER BY 字段, ... DESC 例: orderByDesc("id", "name") ---> order by id DESC,name DESC 测试例: for (User user : users) { System.out.println(user); } } } package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testWrapper() { QueryWrapper wrapper = new QueryWrapper<>(); 5.5、逻辑查询 or 拼接 OR 主动调 or 表示紧接着下个法不是 and 连接!(不调 or 则默认为使 and 连接) and AND 嵌套 例: and(i -> i.eq("name", "李").ne("status", "活着")) ---> and (name = '李 ' and status <> '活着') 测试例: //SELECT id,user_name,password,name,age,email FROM tb_user ORDER BY age DESC wrapper.orderByDesc("age"); List users = this.userMapper.selectList(wrapper); for (User user : users) { System.out.println(user); } } } package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testWrapper() { QueryWrapper wrapper = new QueryWrapper<>(); 5.6、select 在MP查询中,默认查询所有的字段,如果有需要也可以通过select法进指定字段。 //SELECT id,user_name,password,name,age,email FROM tb_user WHERE name = ? OR age = ? wrapper.eq("name","jack").or().eq("age", 24); List users = this.userMapper.selectList(wrapper); for (User user : users) { System.out.println(user); } } } package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testWrapper() { QueryWrapper wrapper = new QueryWrapper<>(); //SELECT id,name,age FROM tb_user WHERE name = ? OR age = ? wrapper.eq("name", "jack") .or() .eq("age", 24) .select("id", "name", "age"); List users = this.userMapper.selectList(wrapper); 6. ActiveRecord ActiveRecord(简称AR)直受动态语( PHP 、 Ruby 等)的喜爱, Java 作为准静态语,对 于 ActiveRecord 往往只能感叹其优雅,所以我们也在 AR 道路上进了定的探索,希望家能够喜 欢。 什么是ActiveRecord? ActiveRecord也属于ORM(对象关系映射)层,由Rails最早提出,遵循标准的ORM模型:表映 射到记录,记录映射到对象,字段映射到对象属性。配合遵循的命名和配置惯例,能够很程度 的快速实现模型的操作,且简洁易懂。 ActiveRecord的主要思想是: 每个数据库表对应创建个类,类的每个对象实例对应于数据库中表的记录;通常 表的每个字段在类中都有相应的Field; ActiveRecord同时负责把持久化,在ActiveRecord中封装了对数据库的访问,即 CURD;; ActiveRecord是种领域模型(Domain Model),封装了部分业务逻辑; 6.1、开启AR之旅 在MP中,开启AR常简单,只需要将实体对象继承Model即可。 for (User user : users) { System.out.println(user); } } } package com.lagou.mp.pojo; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.extension.activerecord.Model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class User extends Model { 6.2、根据主键查询 6.3、新增数据 private Long id; private String userName; private String password; private String name; private Integer age; private String email; } @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testAR() { User user = new User(); user.setId(2L); User user2 = user.selectById(); System.out.println(user2); } } @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testARInsert() { User user = new User(); user.setName("应颠"); user.setAge(30); user.setEmail("yingdian@lagou.cn"); boolean insert = user.insert(); 结果: 6.4、更新操作 结果: System.out.println(insert); } } [main] [com.lagou.mp.mapper.UserMapper.insert]-[DEBUG] ==> Preparing: INSERT INTO tb_user ( name, age, email ) VALUES ( ?, ?, ?, ?, ? ) [main] [com.lagou.mp.mapper.UserMapper.insert]-[DEBUG] ==> Parameters: 应癫 (String), 30(Integer), liubei@lagou.cn(String) [main] [com.lagou.mp.mapper.UserMapper.insert]-[DEBUG] <== Updates: 1 @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testAR() { User user = new User(); user.setId(8L); user.setAge(35); boolean update = user.updateById(); System.out.println(update); } } 6.5、删除操作 结果: [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] ==> Preparing: UPDATE tb_user SET age=? WHERE id=? [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] ==> Parameters: 35(Integer), 8(Long) [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] <== Updates: 1 @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testAR() { User user = new User(); user.setId(7L); boolean delete = user.deleteById(); System.out.println(delete); } } [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] ==> Preparing: DELETE FROM tb_user WHERE id=? [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] ==> Parameters: 7(Long) [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] <== Updates: 1 6.6、根据条件查询 结果: 7. 插件 7.1、mybatis的插件机制 MyBatis 允许你在已映射语句执过程中的某点进拦截调。默认情况下,MyBatis 允许使插件 来拦截的法调包括: 1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) 2. ParameterHandler (getParameterObject, setParameters) 3. ResultSetHandler (handleResultSets, handleOutputParameters) 4. StatementHandler (prepare, parameterize, batch, update, query) 我们看到了可以拦截Executor接的部分法,如update,query,commit,rollback等法,还有 其他接的些法等。 总体概括为: 1. 拦截执器的法 @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testARFindById() { User user = new User(); QueryWrapper userQueryWrapper = new QueryWrapper<>(); userQueryWrapper.le("age","20"); List users = user.selectList(userQueryWrapper); for (User user1 : users) { System.out.println(user1); } } } User(id=1, name=Jone, age=18, email=test1@baomidou.com) User(id=2, name=Jack, age=20, email=test2@baomidou.com) User(id=7, name=慕, age=18, email=test@lagou.cn) 2. 拦截参数的处理 3. 拦截结果集的处理 4. 拦截Sql语法构建的处理 拦截器示例: 注到Spring容器: 或者通过xml配置,mybatis-config.xml: package com.lagou.mp.plugins; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; import java.util.Properties; @Intercepts({@Signature( type= Executor.class, method = "update", args = {MappedStatement.class,Object.class})}) public class MyInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { //拦截法,具体业务逻辑编写的位置 return invocation.proceed(); } @Override public Object plugin(Object target) { //创建target对象的代理对象,的是将当前拦截器加到该对象中 return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { //属性设置 } } /** * 定义拦截器 */ @Bean public MyInterceptor myInterceptor(){ return new MyInterceptor(); } 7.2、执分析插件 在MP中提供了对SQL执的分析的插件,可作阻断全表更新、删除的操作,注意:该插件仅适于开 发环境,不适于产环境。 SpringBoot配置: 测试: 结果:  @Bean public SqlExplainInterceptor sqlExplainInterceptor(){ SqlExplainInterceptor sqlExplainInterceptor = new SqlExplainInterceptor(); List sqlParserList = new ArrayList<>(); // 攻击 SQL 阻断解析器、加解析链 sqlParserList.add(new BlockAttackSqlParser()); sqlExplainInterceptor.setSqlParserList(sqlParserList); return sqlExplainInterceptor; } @Test public void testUpdate(){ User user = new User(); user.setAge(20); int result = this.userMapper.update(user, null); System.out.println("result = " + result); } 可以看到,当执全表更新时,会抛出异常,这样有效防了些误操作。 7.3、性能分析插件 性能分析拦截器,于输出每条 SQL 语句及其执时间,可以设置最执时间,超过时间会抛出异 常。 该插件只于开发环境,不建议产环境使。 配置: javaconfig式 Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of table update operation at com.baomidou.mybatisplus.core.toolkit.ExceptionUtils.mpe(ExceptionUtils.java:4 9) at com.baomidou.mybatisplus.core.toolkit.Assert.isTrue(Assert.java:38) at com.baomidou.mybatisplus.core.toolkit.Assert.notNull(Assert.java:72) at com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser.processUpdate( BlockAttackSqlParser.java:45) at com.baomidou.mybatisplus.core.parser.AbstractJsqlParser.processParser(Abstract JsqlParser.java:92) at com.baomidou.mybatisplus.core.parser.AbstractJsqlParser.parser(AbstractJsqlPar ser.java:67) at com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler.sqlParser (AbstractSqlParserHandler.java:76) at com.baomidou.mybatisplus.extension.plugins.SqlExplainInterceptor.intercept(Sql ExplainInterceptor.java:63) at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61) at com.sun.proxy.$Proxy70.update(Unknown Source) at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession. java:197) ... 41 more xml式 执结果: 可以看到,执时间为11ms。如果将maxTime设置为1,那么,该操作会抛出异常。 @Bean public PerformanceInterceptor performanceInterceptor(){ PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor(); performanceInterceptor.setMaxTime(100); performanceInterceptor.setFormat(true); return performanceInterceptor; }    Time:11 ms - ID:com.lagou.mp.mapper.UserMapper.selectById Execute SQL: SELECT id, user_name, password, name, age, email FROM tb_user WHERE id=7 7.4、乐观锁插件 7.4.1、主要适场景 意图: 当要更新条记录的时候,希望这条记录没有被别更新 乐观锁实现式: 取出记录时,获取当前version 更新时,带上这个version 执更新时, set version = newVersion where version = oldVersion 如果version不对,就更新失败 7.4.2、插件配置 spring xml: spring boot: 7.4.3、注解实体字段 需要为实体字段添加@Version注解。 第步,为表添加version字段,并且设置初始值为1: Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: The SQL execution time is too large, please optimize ! at com.baomidou.mybatisplus.core.toolkit.ExceptionUtils.mpe(ExceptionUtils.java:4 9) at com.baomidou.mybatisplus.core.toolkit.Assert.isTrue(Assert.java:38) ................ @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor() { return new OptimisticLockerInterceptor(); } 第步,为User实体对象添加version字段,并且添加@Version注解: 7.4.4、测试 测试例: 执志: ALTER TABLE `tb_user` ADD COLUMN `version` int(10) NULL AFTER `email`; UPDATE `tb_user` SET `version`='1'; @Version private Integer version; @Test public void testUpdate(){ User user = new User(); user.setAge(30); user.setId(2L); user.setVersion(1); //获取到version为1 int result = this.userMapper.updateById(user); System.out.println("result = " + result); } main] [com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser]- [DEBUG] Original SQL: UPDATE tb_user SET age=?, version=? WHERE id=? AND version=? [main] [com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser]- [DEBUG] parser sql: UPDATE tb_user SET age = ?, version = ? WHERE id = ? AND version = ? [main] [org.springframework.jdbc.datasource.DataSourceUtils]-[DEBUG] Fetching JDBC Connection from DataSource [main] [org.mybatis.spring.transaction.SpringManagedTransaction]-[DEBUG] JDBC Connection [HikariProxyConnection@540206885 wrapping com.mysql.jdbc.JDBC4Connection@27e0f2f5] will not be managed by Spring [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] ==> Preparing: UPDATE tb_user SET age=?, version=? WHERE id=? AND version=? [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] ==> Parameters: 30(Integer), 2(Integer), 2(Long), 1(Integer) [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] <== Updates: 1 [main] [org.mybatis.spring.SqlSessionUtils]-[DEBUG] Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@30135202] result = 1 可以看到,更新的条件中有version条件,并且更新的version为2。 如果再次执,更新则不成功。这样就避免了多同时更新时导致数据的不致。 7.4.5、特别说明 持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime 整数类型下 newVersion = oldVersion + 1 newVersion 会回写到 entity 中 仅持 updateById(id) 与 update(entity, wrapper) 法 在 update(entity, wrapper) 法下, wrapper 不能复!!! 8. Sql 注器 我们已经知道,在MP中,通过AbstractSqlInjector将BaseMapper中的法注到了Mybatis容器,这 样这些法才可以正常执。 那么,如果我们需要扩充BaseMapper中的法,该如何实现呢? 下我们以扩展findAll法为例进学习。 8.1、编写MyBaseMapper 其他的Mapper都可以继承该Mapper,这样实现了统的扩展。 如: 8.2、编写MySqlInjector package com.lagou.mp.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import java.util.List; public interface MyBaseMapper extends BaseMapper { List findAll(); } package com.lagou.mp.mapper; import com.lagou.mp.pojo.User; public interface UserMapper extends MyBaseMapper { User findById(Long id); } 如果直接继承AbstractSqlInjector的话,原有的BaseMapper中的法将失效,所以我们选择继承 DefaultSqlInjector进扩展。 8.3、编写FindAll package com.lagou.mp.sqlInjector; import com.baomidou.mybatisplus.core.injector.AbstractMethod; import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector; import java.util.List; public class MySqlInjector extends DefaultSqlInjector { @Override public List getMethodList() { List methodList = super.getMethodList(); methodList.add(new FindAll()); // 再扩充定义的法 list.add(new FindAll()); return methodList; } } package com.lagou.mp.sqlInjector; import com.baomidou.mybatisplus.core.enums.SqlMethod; import com.baomidou.mybatisplus.core.injector.AbstractMethod; import com.baomidou.mybatisplus.core.metadata.TableInfo; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlSource; public class FindAll extends AbstractMethod { @Override public MappedStatement injectMappedStatement(Class mapperClass, Class modelClass, TableInfo tableInfo) { String sqlMethod = "findAll"; String sql = "select * from " + tableInfo.getTableName(); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); return this.addSelectMappedStatement(mapperClass, sqlMethod, sqlSource, modelClass, tableInfo); } 8.4、注册到Spring容器 8.5、测试 输出的SQL: 此,我们实现了全局扩展SQL注器。 9. 动填充功能 有些时候我们可能会有这样的需求,插或者更新数据时,希望有些字段可以动填充数据,如密 码、version等。在MP中提供了这样的功能,可以实现动填充。 9.1、添加@TableField注解 为email添加动填充功能,在新增数据时有效。 FieldFill提供了多种模式选择: } /** * 定义SQL注器 */ @Bean public MySqlInjector mySqlInjector(){ return new MySqlInjector(); } @Test public void testFindAll(){ List users = this.userMapper.findAll(); for (User user : users) { System.out.println(user); } } [main] [com.lagou.mp.mapper.UserMapper.findAll]-[DEBUG] ==> Preparing: select * from tb_user [main] [com.lagou.mp.mapper.UserMapper.findAll]-[DEBUG] ==> Parameters: [main] [com.lagou.mp.mapper.UserMapper.findAll]-[DEBUG] <== Total: 10 @TableField(fill = FieldFill.INSERT) //插数据时进填充 private String version; public enum FieldFill { 9.2、编写MyMetaObjectHandler 9.3、测试 /** * 默认不处理 */ DEFAULT, /** * 插时填充字段 */ INSERT, /** * 更新时填充字段 */ UPDATE, /** * 插和更新时填充字段 */ INSERT_UPDATE } package com.lagou.mp.handler; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component; @Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { Object password = getFieldValByName("version", metaObject); if(null == password){ //字段为空,可以进填充 setFieldValByName("version", "123456", metaObject); } } @Override public void updateFill(MetaObject metaObject) { } } 结果: 10. 逻辑删除 开发系统时,有时候在实现功能时,删除操作需要实现逻辑删除,所谓逻辑删除就是将数据标记为删 除,并真正的物理删除(DELETE操作),查询时需要携带状态条件,确保被标记的数据不被查 询到。这样做的的就是避免数据被真正的删除。 MP就提供了这样的功能,便我们使,接下来我们起学习下。 10.1、修改表结构 为tb_user表增加deleted字段,于表示数据是否被删除,1代表删除,0代表未删除。 同时,也修改User实体,增加deleted属性并且添加@TableLogic注解: 10.2、配置 @Test public void testInsert(){ User user = new User(); user.setName("冰冰"); user.setAge(30); user.setVersion(1); int result = this.userMapper.insert(user); System.out.println("result = " + result); } ALTER TABLE `tb_user` ADD COLUMN `deleted` int(1) NULL DEFAULT 0 COMMENT '1代表删除,0代表未删除' AFTER `version`; @TableLogic private Integer deleted; application.properties: 10.3、测试 执的SQL: 测试查询: 执的SQL: # 逻辑已删除值(默认为 1) mybatis-plus.global-config.db-config.logic-delete-value=1 # 逻辑未删除值(默认为 0) mybatis-plus.global-config.db-config.logic-not-delete-value=0 @Test public void testDeleteById(){ this.userMapper.deleteById(2L); } [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] ==> Preparing: UPDATE tb_user SET deleted=1 WHERE id=? AND deleted=0 [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] ==> Parameters: 2(Long) [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] <== Updates: 1 @Test public void testSelectById(){ User user = this.userMapper.selectById(2L); System.out.println(user); } 可,已经实现了逻辑删除。 ### 11. 代码成器 AutoGenerator 是 MyBatis-Plus 的代码成器,通过 AutoGenerator 可以快速成 Entity、 Mapper、Mapper XML、Service、Controller 等各个模块的代码,极的提升了开发效率。 11.1、创建程 pom.xml: [main] [com.lagou.mp.mapper.UserMapper.selectById]-[DEBUG] ==> Preparing: SELECT id,user_name,password,name,age,email,version,deleted FROM tb_user WHERE id=? AND deleted=0 [main] [com.lagou.mp.mapper.UserMapper.selectById]-[DEBUG] ==> Parameters: 2(Long) [main] [com.lagou.mp.mapper.UserMapper.selectById]-[DEBUG] <== Total: 0  4.0.0 org.springframework.boot spring-boot-starter-parent 2.3.4.RELEASE  com.lagou lagou-mp-generator 0.0.1-SNAPSHOT lagou-mp-generator Demo project for Spring Boot 11 org.springframework.boot spring-boot-starter-test test  com.baomidou mybatis-plus-boot-starter 3.1.1 com.baomidou mybatis-plus-generator 3.1.1 org.springframework.boot spring-boot-starter-freemarker  mysql mysql-connector-java 5.1.47 org.slf4j slf4j-log4j12 org.springframework.boot spring-boot-starter-web org.projectlomboklomboktrueorg.springframework.bootspring-boot-maven-plugin 11.2、代码 package com.lagou.mp.generator; import java.util.ArrayList; import java.util.List; import java.util.Scanner; import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException; import com.baomidou.mybatisplus.core.toolkit.StringPool; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.generator.AutoGenerator; import com.baomidou.mybatisplus.generator.InjectionConfig; import com.baomidou.mybatisplus.generator.config.DataSourceConfig; import com.baomidou.mybatisplus.generator.config.FileOutConfig; import com.baomidou.mybatisplus.generator.config.GlobalConfig; import com.baomidou.mybatisplus.generator.config.PackageConfig; import com.baomidou.mybatisplus.generator.config.StrategyConfig; import com.baomidou.mybatisplus.generator.config.TemplateConfig; import com.baomidou.mybatisplus.generator.config.po.TableInfo; import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; /** *
*/ INPUT(2), /* 以下3种类型、只有当插对象ID 为空,才动填充。 */ /** * 全局唯ID (idWorker) */ ID_WORKER(3), /** * 全局唯ID (UUID) */ UUID(4), /** * 字符串全局唯ID (idWorker 的字符串表示) */ ID_WORKER_STR(5); private final int key; IdType(int key) { this.key = key; } } package com.lagou.mp.pojo; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor @TableName("tb_user") public class User { @TableId(type = IdType.AUTO) //指定id类型为增 private Long id; 数据插成功: @TableField 在MP中通过@TableField注解可以指定字段的些属性,常常解决的问题有2个: 1、对象中的属性名和字段名不致的问题(驼峰) 2、对象中的属性字段在表中不存在的问题 使: 其他法,如字段不加查询字段: private String userName; private String password; private String name; private Integer age; private String email; } 效果: 3.2 更新操作 在MP中,更新操作有2种,种是根据id更新,另种是根据条件更新。 根据id更新 法定义: 测试: /** * 根据 ID 修改 * * @param entity 实体对象 */ int updateById(@Param(Constants.ENTITY) T entity); @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { 结果: 根据条件更新 法定义: 测试例: @Autowired private UserMapper userMapper; @Test public void testUpdateById() { User user = new User(); user.setId(6L); //主键 user.setAge(21); //更新的字段 //根据id更新,更新不为null的字段 this.userMapper.updateById(user); } } /** * 根据 whereEntity 条件,更新记录 * * @param entity 实体对象 (set 条件值,可以为 null) * @param updateWrapper 实体对象封装操作类(可以为 null,的 entity 于成 where 语句) */ int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper updateWrapper); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 或者,通过UpdateWrapper进更新: 测试结果: import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import net.minidev.json.writer.UpdaterMapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testUpdate() { User user = new User(); user.setAge(22); //更新的字段 //更新的条件 QueryWrapper wrapper = new QueryWrapper<>(); wrapper.eq("id", 6); //执更新操作 int result = this.userMapper.update(user, wrapper); System.out.println("result = " + result); } } @Test public void testUpdate() { //更新的条件以及字段 UpdateWrapper wrapper = new UpdateWrapper<>(); wrapper.eq("id", 6).set("age", 23); //执更新操作 int result = this.userMapper.update(null, wrapper); System.out.println("result = " + result); } 均可达到更新的效果。 关于wrapper更多的法后会详细讲解。 3.3 删除操作 deleteById 法定义: 测试例: [main] [com.lagou.mp.mapper.UserMapper.update]-[DEBUG] ==> Preparing: UPDATE tb_user SET age=? WHERE id = ? [main] [com.lagou.mp.mapper.UserMapper.update]-[DEBUG] ==> Parameters: 23(Integer), 6(Integer) [main] [com.lagou.mp.mapper.UserMapper.update]-[DEBUG] <== Updates: 1 /** * 根据 ID 删除 * * @param id 主键ID */ int deleteById(Serializable id); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testDeleteById() { //执删除操作 int result = this.userMapper.deleteById(6L); System.out.println("result = " + result); } 结果: 数据被删除。 deleteByMap 法定义: 测试例: } [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] ==> Preparing: DELETE FROM tb_user WHERE id=? [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] ==> Parameters: 6(Long) [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] <== Updates: 1 /** * 根据 columnMap 条件,删除记录 * * @param columnMap 表字段 map 对象 */ int deleteByMap(@Param(Constants.COLUMN_MAP) Map<string, object=""> columnMap); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.HashMap; import java.util.Map; @RunWith(SpringRunner.class) 结果: delete 法定义: 测试例: @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testDeleteByMap() { Map<string, object=""> columnMap = new HashMap<>(); columnMap.put("age",21); columnMap.put("name","慕"); //将columnMap中的元素设置为删除的条件,多个之间为and关系 int result = this.userMapper.deleteByMap(columnMap); System.out.println("result = " + result); } } [main] [com.lagou.mp.mapper.UserMapper.deleteByMap]-[DEBUG] ==> Preparing: DELETE FROM tb_user WHERE name = ? AND age = ? [main] [com.lagou.mp.mapper.UserMapper.deleteByMap]-[DEBUG] ==> Parameters:  慕(String), 21(Integer) [main] [com.lagou.mp.mapper.UserMapper.deleteByMap]-[DEBUG] <== Updates: 0 /** * 根据 entity 条件,删除记录 * * @param wrapper 实体对象封装操作类(可以为 null) */ int delete(@Param(Constants.WRAPPER) Wrapper wrapper); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; 结果: 3.3.4、deleteBatchIds 法定义: import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.HashMap; import java.util.Map; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testDeleteByMap() { User user = new User(); user.setAge(20); user.setName("慕"); //将实体对象进包装,包装为操作条件 QueryWrapper wrapper = new QueryWrapper<>(user); int result = this.userMapper.delete(wrapper); System.out.println("result = " + result); } } [main] [com.lagou.mp.mapper.UserMapper.delete]-[DEBUG] ==> Preparing: DELETE FROM tb_user WHERE name=? AND age=? [main] [com.lagou.mp.mapper.UserMapper.delete]-[DEBUG] ==> Parameters: 慕 (String), 20(Integer) [main] [com.lagou.mp.mapper.UserMapper.delete]-[DEBUG] <== Updates: 0 测试例: 结果: /** * 删除(根据ID 批量删除) * * @param idList 主键ID列表(不能为 null 以及 empty) */ int deleteBatchIds(@Param(Constants.COLLECTION) Collection idList); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.Arrays; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testDeleteByMap() { //根据id集合批量删除 int result = this.userMapper.deleteBatchIds(Arrays.asList(1L,10L,20L)); System.out.println("result = " + result); } } [main] [com.lagou.mp.mapper.UserMapper.deleteBatchIds]-[DEBUG] ==> Preparing: DELETE FROM tb_user WHERE id IN ( ? , ? , ? ) [main] [com.lagou.mp.mapper.UserMapper.deleteBatchIds]-[DEBUG] ==> Parameters: 1(Long), 10(Long), 20(Long) [main] [com.lagou.mp.mapper.UserMapper.deleteBatchIds]-[DEBUG] <== Updates: 1 3.4 查询操作 MP提供了多种查询操作,包括根据id查询、批量查询、查询单条数据、查询列表、分查询等操作。 3.4.1、selectById 法定义: 测试例: 结果: 3.4.2、selectBatchIds /** * 根据 ID 查询 * * @param id 主键ID */ T selectById(Serializable id); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelectById() { //根据id查询数据 User user = this.userMapper.selectById(2L); System.out.println("result = " + user); } } result = User(id=2, name=Jack, age=20, email=test2@baomidou.com) 法定义: 测试例: 结果: /** * 查询(根据ID 批量查询) * * @param idList 主键ID列表(不能为 null 以及 empty) */ List selectBatchIds(@Param(Constants.COLLECTION) Collection idList); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.Arrays; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelectBatchIds() { //根据id集合批量查询 List users = this.userMapper.selectBatchIds(Arrays.asList(2L, 3L, 10L)); for (User user : users) { System.out.println(user); } } } User(id=2, name=Jack, age=20, email=test2@baomidou.com) User(id=3, name=Tom, age=28, email=test3@baomidou.com) 3.4.3、selectOne 法定义: 测试例: 结果: 3.4.4、selectCount /** * 根据 entity 条件,查询条记录 * * @param queryWrapper 实体对象封装操作类(可以为 null) */ T selectOne(@Param(Constants.WRAPPER) Wrapper queryWrapper); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelectOne() { QueryWrapper wrapper = new QueryWrapper(); wrapper.eq("name", "jack"); //根据条件查询条数据,如果结果超过条会报错 User user = this.userMapper.selectOne(wrapper); System.out.println(user); } } User(id=2, name=Jack, age=20, email=test2@baomidou.com) 法定义: 测试例: 结果: 3.4.5、selectList 法定义: /** * 根据 Wrapper 条件,查询总记录数 * * @param queryWrapper 实体对象封装操作类(可以为 null) */ Integer selectCount(@Param(Constants.WRAPPER) Wrapper queryWrapper); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelectCount() { QueryWrapper wrapper = new QueryWrapper(); wrapper.gt("age", 23); //年龄于23岁 //根据条件查询数据条数 Integer count = this.userMapper.selectCount(wrapper); System.out.println("count = " + count); } } count = 2 测试例: 结果: /** * 根据 entity 条件,查询全部记录 * * @param queryWrapper 实体对象封装操作类(可以为 null) */ List selectList(@Param(Constants.WRAPPER) Wrapper queryWrapper); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelectList() { QueryWrapper wrapper = new QueryWrapper(); wrapper.gt("age", 23); //年龄于23岁 //根据条件查询数据 List users = this.userMapper.selectList(wrapper); for (User user : users) { System.out.println("user = " + user); } } } user = User(id=3, name=Tom, age=28, email=test3@baomidou.com) user = User(id=5, name=Billie, age=24, email=test5@baomidou.com) 3.4.6、selectPage 法定义: 配置分插件: 测试例: /** * 根据 entity 条件,查询全部记录(并翻) * * @param page 分查询条件(可以为 RowBounds.DEFAULT) * @param queryWrapper 实体对象封装操作类(可以为 null) */ IPage selectPage(IPage page, @Param(Constants.WRAPPER) Wrapper queryWrapper); package com.lagou.mp; 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(); } } package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; 结果: 3.5 SQL注的原理 前我们已经知道,MP在启动后会将BaseMapper中的系列的法注册到meppedStatements中, 那么究竟是如何注的呢?流程是怎么样的?下我们将起来分析下。 在MP中,ISqlInjector负责SQL的注作,它是个接,AbstractSqlInjector是它的实现类,实现 关系如下: import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelectPage() { QueryWrapper wrapper = new QueryWrapper(); wrapper.gt("age", 20); //年龄于20岁 Page page = new Page<>(1,1); //根据条件查询数据 IPage iPage = this.userMapper.selectPage(page, wrapper); System.out.println("数据总条数:" + iPage.getTotal()); System.out.println("总数:" + iPage.getPages()); List users = iPage.getRecords(); for (User user : users) { System.out.println("user = " + user); } } } 数据总条数:4 总数:4 user = User(id=3, name=Tom, age=28, email=test3@baomidou.com) 在AbstractSqlInjector中,主要是由inspectInject()法进注的,如下: 在实现法中, methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo)); 是关键,循环遍历法,进注。 最终调抽象法injectMappedStatement进真正的注: @Override public void inspectInject(MapperBuilderAssistant builderAssistant, Class mapperClass) { Class modelClass = extractModelClass(mapperClass); if (modelClass != null) { String className = mapperClass.toString(); Set mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration()); if (!mapperRegistryCache.contains(className)) { List methodList = this.getMethodList(); if (CollectionUtils.isNotEmpty(methodList)) { TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass); // 循环注定义法 methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo)); } else { logger.debug(mapperClass.toString() + ", No effective injection method was found."); } mapperRegistryCache.add(className); } } } 查看该法的实现: 以SelectById为例查看: /** * 注定义 MappedStatement * * @param mapperClass mapper 接 * @param modelClass mapper 泛型 * @param tableInfo 数据库表反射信息 * @return MappedStatement */ public abstract MappedStatement injectMappedStatement(Class mapperClass, Class modelClass, TableInfo tableInfo); 可以看到,成了SqlSource对象,再将SQL通过addSelectMappedStatement法添加到 meppedStatements中。 4. 配置 在MP中有量的配置,其中有部分是Mybatis原的配置,另部分是MP的配置,详情:https://m ybatis.plus/config/ 下我们对常的配置做讲解。 4.1、基本配置 4.1.1、configLocation MyBatis 配置件位置,如果有单独的 MyBatis 配置,请将其路径配置到 configLocation 中。 MyBatis Configuration 的具体内容请参考MyBatis 官档 Spring Boot: public class SelectById extends AbstractMethod { @Override public MappedStatement injectMappedStatement(Class mapperClass, Class modelClass, TableInfo tableInfo) { SqlMethod sqlMethod = SqlMethod.LOGIC_SELECT_BY_ID; SqlSource sqlSource = new RawSqlSource(configuration, String.format(sqlMethod.getSql(), sqlSelectColumns(tableInfo, false), tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getKeyProperty(), tableInfo.getLogicDeleteSql(true, false)), Object.class); return this.addSelectMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource, modelClass, tableInfo); } } mybatis-plus.config-location = classpath:mybatis-config.xml Spring MVC: 4.1.2、mapperLocations MyBatis Mapper 所对应的 XML 件位置,如果您在 Mapper 中有定义法(XML 中有定义实 现),需要进该配置,告诉 Mapper 所对应的 XML 件位置。 Spring Boot: Spring MVC: Maven 多模块项的扫描路径需以 classpath*: classpath*: 开头 (即加载多个 jar 包下的 XML 件) 测试: UserMapper.xml: mybatis-plus.mapper-locations = classpath*:mybatis/*.xml select * from tb_user where id = #{id}测试例: 运结果: 4.1.3、typeAliasesPackage package com.lagou.mp.mapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.mapper.BaseMapper; public interface UserMapper extends BaseMapper { User findById(Long id); } package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelectPage() { User user = this.userMapper.findById(2L); System.out.println(user); } } MyBaits 别名包扫描路径,通过该属性可以给包中的类注册别名,注册后在 Mapper 对应的 XML 件 中可以直接使类名,不使全限定的类名(即 XML 中调的时候不包含包名)。 Spring Boot: Spring MVC: 4.2、进阶配置 本部分(Configuration)的配置都为 MyBatis 原持的配置,这意味着您可以通过 MyBatis XML 配置件的形式进配置。 4.2.1、mapUnderscoreToCamelCase 类型: boolean 默认值: true 是否开启动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN(下划线命名) 到 经典 Java 属性名 aColumn(驼峰命名) 的类似映射。 注意: 此属性在 MyBatis 中原默认值为 false,在 MyBatis-Plus 中,此属性也将于成最终的 SQL 的 select body 如果您的数据库命名符合规则需使 @TableField 注解指定数据库字段名 示例(SpringBoot): 4.2.2、cacheEnabled 类型: boolean 默认值: true 全局地开启或关闭配置件中的所有映射器已经配置的任何缓存,默认为 true。 示例: mybatis-plus.type-aliases-package = com.lagou.mp.pojo #关闭动驼峰映射,该参数不能和mybatis-plus.config-location同时存在 mybatis-plus.configuration.map-underscore-to-camel-case=false 4.3、DB 策略配置 4.3.1、idType 类型: com.baomidou.mybatisplus.annotation.IdType 默认值: ID_WORKER 全局默认主键类型,设置后,即可省略实体对象中的@TableId(type = IdType.AUTO)配置。 示例: SpringBoot: SpringMVC: 4.3.2、tablePrefix 类型: String 默认值: null 表名前缀,全局配置后可省略@TableName()配置。 SpringBoot: SpringMVC: mybatis-plus.configuration.cache-enabled=false mybatis-plus.global-config.db-config.id-type=auto  mybatis-plus.global-config.db-config.table-prefix=tb_ 5. 条件构造器 在MP中,Wrapper接的实现类关系如下: 可以看到,AbstractWrapper和AbstractChainWrapper是重点实现,接下来我们重点学习 AbstractWrapper以及其类。 说明: QueryWrapper(LambdaQueryWrapper) 和 UpdateWrapper(LambdaUpdateWrapper) 的类 于成 sql 的 where 条件, entity 属性也于成 sql 的 where 条件 注意: entity 成的 where 条件与 使各个 api 成的 where 条件没有任何关联为 官档地址:https://mybatis.plus/guide/wrapper.html 5.1、allEq 5.1.1、说明 全部eq(或个别isNull) 个别参数说明: params : key 为数据库字段名, value 为字段值 null2IsNull : 为 true 则在 map 的 value 为 null 时调 isNull 法,为 false 时则忽 略 value 为 null 的 例1: allEq({id:1,name:"王",age:null}) ---> id = 1 and name = '王' and age is null 例2: allEq({id:1,name:"王",age:null}, false) ---> id = 1 and name = '王' 个别参数说明: filter : 过滤函数,是否允许字段传对条件中 params 与 null2IsNull : 同上 例1: allEq((k,v) -> k.indexOf("a") > 0, {id:1,name:"王",age:null}) --- > name = '王' and age is null 例2: allEq((k,v) -> k.indexOf("a") > 0, {id:1,name:"王",age:null}, false) ---> name = '王' 5.1.2、测试例 allEq(Map<r, v=""> params) allEq(Map<r, v=""> params, boolean null2IsNull) allEq(boolean condition, Map<r, v=""> params, boolean null2IsNull) allEq(BiPredicate<r, v=""> filter, Map<r, v=""> params) allEq(BiPredicate<r, v=""> filter, Map<r, v=""> params, boolean null2IsNull) allEq(boolean condition, BiPredicate<r, v=""> filter, Map<r, v=""> params, boolean null2IsNull) package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.HashMap; import java.util.List; import java.util.Map; 5.2、基本较操作 eq 等于 = ne 不等于 <> gt 于 > ge 于等于 >= lt 于 < le @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testWrapper() { QueryWrapper wrapper = new QueryWrapper<>(); //设置条件 Map<string,object> params = new HashMap<>(); params.put("name", "jack"); params.put("age", "20"); // wrapper.allEq(params);//SELECT * FROM tb_user WHERE password IS NULL AND name = ? AND age = ? // wrapper.allEq(params,false); //SELECT * FROM tb_user WHERE name = ? AND age = ? // wrapper.allEq((k, v) -> (k.equals("name") || k.equals("age")) ,params);//SELECT * FROM tb_user WHERE name = ? AND age = ? List users = this.userMapper.selectList(wrapper); for (User user : users) { System.out.println(user); } } } 于等于 <= between BETWEEN 值1 AND 值2 notBetween NOT BETWEEN 值1 AND 值2 in 字段 IN (value.get(0), value.get(1), ...) notIn 字段 NOT IN (v0, v1, ...) 测试例: package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; QueryWrapper wrapper = new QueryWrapper<>(); //SELECT id,name,age,email FROM tb_user WHERE password = ? AND age >= ? AND name IN (?,?,?) wrapper.eq("email", "test2@baomidou.com") .ge("age", 20) .in("name", "jack", "jone", "tom"); List users = this.userMapper.selectList(wrapper); for (User user : users) { System.out.println(user); } } } 5.3、模糊查询 like LIKE '%值%' 例: like("name", "王") ---> name like '%王%' notLike NOT LIKE '%值%' 例: notLike("name", "王") ---> name not like '%王%' likeLeft LIKE '%值' 例: likeLeft("name", "王") ---> name like '%王' likeRight LIKE '值%' 例: likeRight("name", "王") ---> name like '王%' 测试例: package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testWrapper() { QueryWrapper wrapper = new QueryWrapper<>(); //SELECT id,user_name,password,name,age,email FROM tb_user WHERE name LIKE ? //Parameters: %%(String) wrapper.like("name", ""); List users = this.userMapper.selectList(wrapper); 5.4、排序 orderBy 排序:ORDER BY 字段, ... 例: orderBy(true, true, "id", "name") ---> order by id ASC,name ASC orderByAsc 排序:ORDER BY 字段, ... ASC 例: orderByAsc("id", "name") ---> order by id ASC,name ASC orderByDesc 排序:ORDER BY 字段, ... DESC 例: orderByDesc("id", "name") ---> order by id DESC,name DESC 测试例: for (User user : users) { System.out.println(user); } } } package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testWrapper() { QueryWrapper wrapper = new QueryWrapper<>(); 5.5、逻辑查询 or 拼接 OR 主动调 or 表示紧接着下个法不是 and 连接!(不调 or 则默认为使 and 连接) and AND 嵌套 例: and(i -> i.eq("name", "李").ne("status", "活着")) ---> and (name = '李 ' and status <> '活着') 测试例: //SELECT id,user_name,password,name,age,email FROM tb_user ORDER BY age DESC wrapper.orderByDesc("age"); List users = this.userMapper.selectList(wrapper); for (User user : users) { System.out.println(user); } } } package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testWrapper() { QueryWrapper wrapper = new QueryWrapper<>(); 5.6、select 在MP查询中,默认查询所有的字段,如果有需要也可以通过select法进指定字段。 //SELECT id,user_name,password,name,age,email FROM tb_user WHERE name = ? OR age = ? wrapper.eq("name","jack").or().eq("age", 24); List users = this.userMapper.selectList(wrapper); for (User user : users) { System.out.println(user); } } } package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testWrapper() { QueryWrapper wrapper = new QueryWrapper<>(); //SELECT id,name,age FROM tb_user WHERE name = ? OR age = ? wrapper.eq("name", "jack") .or() .eq("age", 24) .select("id", "name", "age"); List users = this.userMapper.selectList(wrapper); 6. ActiveRecord ActiveRecord(简称AR)直受动态语( PHP 、 Ruby 等)的喜爱, Java 作为准静态语,对 于 ActiveRecord 往往只能感叹其优雅,所以我们也在 AR 道路上进了定的探索,希望家能够喜 欢。 什么是ActiveRecord? ActiveRecord也属于ORM(对象关系映射)层,由Rails最早提出,遵循标准的ORM模型:表映 射到记录,记录映射到对象,字段映射到对象属性。配合遵循的命名和配置惯例,能够很程度 的快速实现模型的操作,且简洁易懂。 ActiveRecord的主要思想是: 每个数据库表对应创建个类,类的每个对象实例对应于数据库中表的记录;通常 表的每个字段在类中都有相应的Field; ActiveRecord同时负责把持久化,在ActiveRecord中封装了对数据库的访问,即 CURD;; ActiveRecord是种领域模型(Domain Model),封装了部分业务逻辑; 6.1、开启AR之旅 在MP中,开启AR常简单,只需要将实体对象继承Model即可。 for (User user : users) { System.out.println(user); } } } package com.lagou.mp.pojo; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.extension.activerecord.Model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class User extends Model { 6.2、根据主键查询 6.3、新增数据 private Long id; private String userName; private String password; private String name; private Integer age; private String email; } @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testAR() { User user = new User(); user.setId(2L); User user2 = user.selectById(); System.out.println(user2); } } @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testARInsert() { User user = new User(); user.setName("应颠"); user.setAge(30); user.setEmail("yingdian@lagou.cn"); boolean insert = user.insert(); 结果: 6.4、更新操作 结果: System.out.println(insert); } } [main] [com.lagou.mp.mapper.UserMapper.insert]-[DEBUG] ==> Preparing: INSERT INTO tb_user ( name, age, email ) VALUES ( ?, ?, ?, ?, ? ) [main] [com.lagou.mp.mapper.UserMapper.insert]-[DEBUG] ==> Parameters: 应癫 (String), 30(Integer), liubei@lagou.cn(String) [main] [com.lagou.mp.mapper.UserMapper.insert]-[DEBUG] <== Updates: 1 @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testAR() { User user = new User(); user.setId(8L); user.setAge(35); boolean update = user.updateById(); System.out.println(update); } } 6.5、删除操作 结果: [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] ==> Preparing: UPDATE tb_user SET age=? WHERE id=? [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] ==> Parameters: 35(Integer), 8(Long) [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] <== Updates: 1 @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testAR() { User user = new User(); user.setId(7L); boolean delete = user.deleteById(); System.out.println(delete); } } [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] ==> Preparing: DELETE FROM tb_user WHERE id=? [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] ==> Parameters: 7(Long) [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] <== Updates: 1 6.6、根据条件查询 结果: 7. 插件 7.1、mybatis的插件机制 MyBatis 允许你在已映射语句执过程中的某点进拦截调。默认情况下,MyBatis 允许使插件 来拦截的法调包括: 1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) 2. ParameterHandler (getParameterObject, setParameters) 3. ResultSetHandler (handleResultSets, handleOutputParameters) 4. StatementHandler (prepare, parameterize, batch, update, query) 我们看到了可以拦截Executor接的部分法,如update,query,commit,rollback等法,还有 其他接的些法等。 总体概括为: 1. 拦截执器的法 @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testARFindById() { User user = new User(); QueryWrapper userQueryWrapper = new QueryWrapper<>(); userQueryWrapper.le("age","20"); List users = user.selectList(userQueryWrapper); for (User user1 : users) { System.out.println(user1); } } } User(id=1, name=Jone, age=18, email=test1@baomidou.com) User(id=2, name=Jack, age=20, email=test2@baomidou.com) User(id=7, name=慕, age=18, email=test@lagou.cn) 2. 拦截参数的处理 3. 拦截结果集的处理 4. 拦截Sql语法构建的处理 拦截器示例: 注到Spring容器: 或者通过xml配置,mybatis-config.xml: package com.lagou.mp.plugins; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; import java.util.Properties; @Intercepts({@Signature( type= Executor.class, method = "update", args = {MappedStatement.class,Object.class})}) public class MyInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { //拦截法,具体业务逻辑编写的位置 return invocation.proceed(); } @Override public Object plugin(Object target) { //创建target对象的代理对象,的是将当前拦截器加到该对象中 return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { //属性设置 } } /** * 定义拦截器 */ @Bean public MyInterceptor myInterceptor(){ return new MyInterceptor(); } 7.2、执分析插件 在MP中提供了对SQL执的分析的插件,可作阻断全表更新、删除的操作,注意:该插件仅适于开 发环境,不适于产环境。 SpringBoot配置: 测试: 结果:  @Bean public SqlExplainInterceptor sqlExplainInterceptor(){ SqlExplainInterceptor sqlExplainInterceptor = new SqlExplainInterceptor(); List sqlParserList = new ArrayList<>(); // 攻击 SQL 阻断解析器、加解析链 sqlParserList.add(new BlockAttackSqlParser()); sqlExplainInterceptor.setSqlParserList(sqlParserList); return sqlExplainInterceptor; } @Test public void testUpdate(){ User user = new User(); user.setAge(20); int result = this.userMapper.update(user, null); System.out.println("result = " + result); } 可以看到,当执全表更新时,会抛出异常,这样有效防了些误操作。 7.3、性能分析插件 性能分析拦截器,于输出每条 SQL 语句及其执时间,可以设置最执时间,超过时间会抛出异 常。 该插件只于开发环境,不建议产环境使。 配置: javaconfig式 Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of table update operation at com.baomidou.mybatisplus.core.toolkit.ExceptionUtils.mpe(ExceptionUtils.java:4 9) at com.baomidou.mybatisplus.core.toolkit.Assert.isTrue(Assert.java:38) at com.baomidou.mybatisplus.core.toolkit.Assert.notNull(Assert.java:72) at com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser.processUpdate( BlockAttackSqlParser.java:45) at com.baomidou.mybatisplus.core.parser.AbstractJsqlParser.processParser(Abstract JsqlParser.java:92) at com.baomidou.mybatisplus.core.parser.AbstractJsqlParser.parser(AbstractJsqlPar ser.java:67) at com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler.sqlParser (AbstractSqlParserHandler.java:76) at com.baomidou.mybatisplus.extension.plugins.SqlExplainInterceptor.intercept(Sql ExplainInterceptor.java:63) at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61) at com.sun.proxy.$Proxy70.update(Unknown Source) at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession. java:197) ... 41 more xml式 执结果: 可以看到,执时间为11ms。如果将maxTime设置为1,那么,该操作会抛出异常。 @Bean public PerformanceInterceptor performanceInterceptor(){ PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor(); performanceInterceptor.setMaxTime(100); performanceInterceptor.setFormat(true); return performanceInterceptor; }    Time:11 ms - ID:com.lagou.mp.mapper.UserMapper.selectById Execute SQL: SELECT id, user_name, password, name, age, email FROM tb_user WHERE id=7 7.4、乐观锁插件 7.4.1、主要适场景 意图: 当要更新条记录的时候,希望这条记录没有被别更新 乐观锁实现式: 取出记录时,获取当前version 更新时,带上这个version 执更新时, set version = newVersion where version = oldVersion 如果version不对,就更新失败 7.4.2、插件配置 spring xml: spring boot: 7.4.3、注解实体字段 需要为实体字段添加@Version注解。 第步,为表添加version字段,并且设置初始值为1: Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: The SQL execution time is too large, please optimize ! at com.baomidou.mybatisplus.core.toolkit.ExceptionUtils.mpe(ExceptionUtils.java:4 9) at com.baomidou.mybatisplus.core.toolkit.Assert.isTrue(Assert.java:38) ................ @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor() { return new OptimisticLockerInterceptor(); } 第步,为User实体对象添加version字段,并且添加@Version注解: 7.4.4、测试 测试例: 执志: ALTER TABLE `tb_user` ADD COLUMN `version` int(10) NULL AFTER `email`; UPDATE `tb_user` SET `version`='1'; @Version private Integer version; @Test public void testUpdate(){ User user = new User(); user.setAge(30); user.setId(2L); user.setVersion(1); //获取到version为1 int result = this.userMapper.updateById(user); System.out.println("result = " + result); } main] [com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser]- [DEBUG] Original SQL: UPDATE tb_user SET age=?, version=? WHERE id=? AND version=? [main] [com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser]- [DEBUG] parser sql: UPDATE tb_user SET age = ?, version = ? WHERE id = ? AND version = ? [main] [org.springframework.jdbc.datasource.DataSourceUtils]-[DEBUG] Fetching JDBC Connection from DataSource [main] [org.mybatis.spring.transaction.SpringManagedTransaction]-[DEBUG] JDBC Connection [HikariProxyConnection@540206885 wrapping com.mysql.jdbc.JDBC4Connection@27e0f2f5] will not be managed by Spring [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] ==> Preparing: UPDATE tb_user SET age=?, version=? WHERE id=? AND version=? [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] ==> Parameters: 30(Integer), 2(Integer), 2(Long), 1(Integer) [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] <== Updates: 1 [main] [org.mybatis.spring.SqlSessionUtils]-[DEBUG] Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@30135202] result = 1 可以看到,更新的条件中有version条件,并且更新的version为2。 如果再次执,更新则不成功。这样就避免了多同时更新时导致数据的不致。 7.4.5、特别说明 持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime 整数类型下 newVersion = oldVersion + 1 newVersion 会回写到 entity 中 仅持 updateById(id) 与 update(entity, wrapper) 法 在 update(entity, wrapper) 法下, wrapper 不能复!!! 8. Sql 注器 我们已经知道,在MP中,通过AbstractSqlInjector将BaseMapper中的法注到了Mybatis容器,这 样这些法才可以正常执。 那么,如果我们需要扩充BaseMapper中的法,该如何实现呢? 下我们以扩展findAll法为例进学习。 8.1、编写MyBaseMapper 其他的Mapper都可以继承该Mapper,这样实现了统的扩展。 如: 8.2、编写MySqlInjector package com.lagou.mp.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import java.util.List; public interface MyBaseMapper extends BaseMapper { List findAll(); } package com.lagou.mp.mapper; import com.lagou.mp.pojo.User; public interface UserMapper extends MyBaseMapper { User findById(Long id); } 如果直接继承AbstractSqlInjector的话,原有的BaseMapper中的法将失效,所以我们选择继承 DefaultSqlInjector进扩展。 8.3、编写FindAll package com.lagou.mp.sqlInjector; import com.baomidou.mybatisplus.core.injector.AbstractMethod; import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector; import java.util.List; public class MySqlInjector extends DefaultSqlInjector { @Override public List getMethodList() { List methodList = super.getMethodList(); methodList.add(new FindAll()); // 再扩充定义的法 list.add(new FindAll()); return methodList; } } package com.lagou.mp.sqlInjector; import com.baomidou.mybatisplus.core.enums.SqlMethod; import com.baomidou.mybatisplus.core.injector.AbstractMethod; import com.baomidou.mybatisplus.core.metadata.TableInfo; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlSource; public class FindAll extends AbstractMethod { @Override public MappedStatement injectMappedStatement(Class mapperClass, Class modelClass, TableInfo tableInfo) { String sqlMethod = "findAll"; String sql = "select * from " + tableInfo.getTableName(); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); return this.addSelectMappedStatement(mapperClass, sqlMethod, sqlSource, modelClass, tableInfo); } 8.4、注册到Spring容器 8.5、测试 输出的SQL: 此,我们实现了全局扩展SQL注器。 9. 动填充功能 有些时候我们可能会有这样的需求,插或者更新数据时,希望有些字段可以动填充数据,如密 码、version等。在MP中提供了这样的功能,可以实现动填充。 9.1、添加@TableField注解 为email添加动填充功能,在新增数据时有效。 FieldFill提供了多种模式选择: } /** * 定义SQL注器 */ @Bean public MySqlInjector mySqlInjector(){ return new MySqlInjector(); } @Test public void testFindAll(){ List users = this.userMapper.findAll(); for (User user : users) { System.out.println(user); } } [main] [com.lagou.mp.mapper.UserMapper.findAll]-[DEBUG] ==> Preparing: select * from tb_user [main] [com.lagou.mp.mapper.UserMapper.findAll]-[DEBUG] ==> Parameters: [main] [com.lagou.mp.mapper.UserMapper.findAll]-[DEBUG] <== Total: 10 @TableField(fill = FieldFill.INSERT) //插数据时进填充 private String version; public enum FieldFill { 9.2、编写MyMetaObjectHandler 9.3、测试 /** * 默认不处理 */ DEFAULT, /** * 插时填充字段 */ INSERT, /** * 更新时填充字段 */ UPDATE, /** * 插和更新时填充字段 */ INSERT_UPDATE } package com.lagou.mp.handler; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component; @Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { Object password = getFieldValByName("version", metaObject); if(null == password){ //字段为空,可以进填充 setFieldValByName("version", "123456", metaObject); } } @Override public void updateFill(MetaObject metaObject) { } } 结果: 10. 逻辑删除 开发系统时,有时候在实现功能时,删除操作需要实现逻辑删除,所谓逻辑删除就是将数据标记为删 除,并真正的物理删除(DELETE操作),查询时需要携带状态条件,确保被标记的数据不被查 询到。这样做的的就是避免数据被真正的删除。 MP就提供了这样的功能,便我们使,接下来我们起学习下。 10.1、修改表结构 为tb_user表增加deleted字段,于表示数据是否被删除,1代表删除,0代表未删除。 同时,也修改User实体,增加deleted属性并且添加@TableLogic注解: 10.2、配置 @Test public void testInsert(){ User user = new User(); user.setName("冰冰"); user.setAge(30); user.setVersion(1); int result = this.userMapper.insert(user); System.out.println("result = " + result); } ALTER TABLE `tb_user` ADD COLUMN `deleted` int(1) NULL DEFAULT 0 COMMENT '1代表删除,0代表未删除' AFTER `version`; @TableLogic private Integer deleted; application.properties: 10.3、测试 执的SQL: 测试查询: 执的SQL: # 逻辑已删除值(默认为 1) mybatis-plus.global-config.db-config.logic-delete-value=1 # 逻辑未删除值(默认为 0) mybatis-plus.global-config.db-config.logic-not-delete-value=0 @Test public void testDeleteById(){ this.userMapper.deleteById(2L); } [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] ==> Preparing: UPDATE tb_user SET deleted=1 WHERE id=? AND deleted=0 [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] ==> Parameters: 2(Long) [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] <== Updates: 1 @Test public void testSelectById(){ User user = this.userMapper.selectById(2L); System.out.println(user); } 可,已经实现了逻辑删除。 ### 11. 代码成器 AutoGenerator 是 MyBatis-Plus 的代码成器,通过 AutoGenerator 可以快速成 Entity、 Mapper、Mapper XML、Service、Controller 等各个模块的代码,极的提升了开发效率。 11.1、创建程 pom.xml: [main] [com.lagou.mp.mapper.UserMapper.selectById]-[DEBUG] ==> Preparing: SELECT id,user_name,password,name,age,email,version,deleted FROM tb_user WHERE id=? AND deleted=0 [main] [com.lagou.mp.mapper.UserMapper.selectById]-[DEBUG] ==> Parameters: 2(Long) [main] [com.lagou.mp.mapper.UserMapper.selectById]-[DEBUG] <== Total: 0  4.0.0 org.springframework.boot spring-boot-starter-parent 2.3.4.RELEASE  com.lagou lagou-mp-generator 0.0.1-SNAPSHOT lagou-mp-generator Demo project for Spring Boot 11 org.springframework.boot spring-boot-starter-test test  com.baomidou mybatis-plus-boot-starter 3.1.1 com.baomidou mybatis-plus-generator 3.1.1 org.springframework.boot spring-boot-starter-freemarker  mysql mysql-connector-java 5.1.47 org.slf4j slf4j-log4j12 org.springframework.boot spring-boot-starter-web org.projectlomboklomboktrueorg.springframework.bootspring-boot-maven-plugin 11.2、代码 package com.lagou.mp.generator; import java.util.ArrayList; import java.util.List; import java.util.Scanner; import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException; import com.baomidou.mybatisplus.core.toolkit.StringPool; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.generator.AutoGenerator; import com.baomidou.mybatisplus.generator.InjectionConfig; import com.baomidou.mybatisplus.generator.config.DataSourceConfig; import com.baomidou.mybatisplus.generator.config.FileOutConfig; import com.baomidou.mybatisplus.generator.config.GlobalConfig; import com.baomidou.mybatisplus.generator.config.PackageConfig; import com.baomidou.mybatisplus.generator.config.StrategyConfig; import com.baomidou.mybatisplus.generator.config.TemplateConfig; import com.baomidou.mybatisplus.generator.config.po.TableInfo; import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; /** *

* mysql 代码成器演示例 *

*/ public class MysqlGenerator { /** *
*/ public class MysqlGenerator { /** *

* 读取控制台内容 *

*/ public static String scanner(String tip) { Scanner scanner = new Scanner(System.in); StringBuilder help = new StringBuilder(); help.append("请输" + tip + ":"); System.out.println(help.toString()); if (scanner.hasNext()) { String ipt = scanner.next(); if (StringUtils.isNotEmpty(ipt)) { return ipt; } } throw new MybatisPlusException("请输正确的" + tip + "!"); } /** * RUN THIS */ public static void main(String[] args) { // 代码成器 AutoGenerator mpg = new AutoGenerator(); // 全局配置 GlobalConfig gc = new GlobalConfig(); String projectPath = System.getProperty("user.dir"); gc.setOutputDir(projectPath + "/src/main/java"); gc.setAuthor("lagou"); gc.setOpen(false); mpg.setGlobalConfig(gc); // 数据源配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://127.0.0.1:3306/mp? useUnicode=true&useSSL=false&characterEncoding=utf8"); // dsc.setSchemaName("public"); dsc.setDriverName("com.mysql.jdbc.Driver"); dsc.setUsername("root"); dsc.setPassword("root"); mpg.setDataSource(dsc); // 包配置 PackageConfig pc = new PackageConfig(); pc.setModuleName(scanner("模块名")); pc.setParent("com.lagou.mp.generator"); mpg.setPackageInfo(pc); // 定义配置 InjectionConfig cfg = new InjectionConfig() { @Override public void initMap() { // to do nothing } }; List focList = new ArrayList<>(); focList.add(new FileOutConfig("/templates/mapper.xml.ftl") { @Override public String outputFile(TableInfo tableInfo) { // 定义输件名称 11.3、测试 return projectPath + "/lagou-mpgenerator/src/main/resources/mapper/" + pc.getModuleName() + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML; } }); cfg.setFileOutConfigList(focList); mpg.setCfg(cfg); mpg.setTemplate(new TemplateConfig().setXml(null)); // 策略配置 StrategyConfig strategy = new StrategyConfig(); strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setColumnNaming(NamingStrategy.underline_to_camel); // strategy.setSuperEntityClass("com.baomidou.mybatisplus.samples.generator.commo n.BaseEntity"); strategy.setEntityLombokModel(true); // strategy.setSuperControllerClass("com.baomidou.mybatisplus.samples.generator.c ommon.BaseController"); strategy.setInclude(scanner("表名")); strategy.setSuperEntityColumns("id"); strategy.setControllerMappingHyphenStyle(true); strategy.setTablePrefix(pc.getModuleName() + "_"); mpg.setStrategy(strategy); // 选择 freemarker 引擎需要指定如下加,注意 pom 依赖必须有! mpg.setTemplateEngine(new FreemarkerTemplateEngine()); mpg.execute(); } } 代码已成: 实体对象: 12. MybatisX 快速开发插件 MybatisX 是款基于 IDEA 的快速开发插件,为效率。 安装法:打开 IDEA,进 File -> Settings -> Plugins -> Browse Repositories,输 mybatisx 搜 索并安装。 功能: Java 与 XML 调回跳转 Mapper 法动成 XML
————————

Mybatis 第部分:定义持久层框架 1.1 分析JDBC操作问题 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? characterEncoding=utf-8”, “root”, “root”); // 定义sql语句?表示占位符 String sql = “select * from user where username = ?”; // 获取预处理statement preparedStatement = connection.prepareStatement(sql); // 设置参数,第个参数为sql语句中参数的序号(从1开始),第个参数为设置的参数值 preparedStatement.setString(1, “tom”); // 向数据库发出sql执查询,查询出结果集 resultSet = preparedStatement.executeQuery(); // 遍历查询结果集 while (resultSet.next()) { int id = resultSet.getInt(“id”); String username = resultSet.getString(“username”); // 封装User user.setId(id); user.setUsername(username); } System.out.println(user); } } catch (Exception e) { e.printStackTrace(); } finally { // 释放资源 if (resultSet != null) { try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); JDBC问题总结: 原始jdbc开发存在的问题如下: 1、 数据库连接创建、释放频繁造成系统资源浪费,从影响系统性能。 2、 Sql语句在代码中硬编码,造成代码不易维护,实际应中sql变化的可能较,sql变动需要改变 java代码。 3、 使preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不定,可能 多也可能少,修改sql还要修改代码,系统不易维护。 4、 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据 库 记录封装成pojo对象解析较便 1.2 问题解决思路 ①使数据库连接池初始化连接资源 ②将sql语句抽取到xml配置件中 ③使反射、内省等底层技术,动将实体与表进属性与字段的动映射 1.3 定义框架设计 使端: 提供核配置件: sqlMapConfig.xml : 存放数据源信息,引mapper.xml Mapper.xml : sql语句的配置件信息 } } if (preparedStatement != null) { try { preparedStatement.close(); } catch (SQLException e) { e.printStackTrace(); } } if (connection != null) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } 框架端: 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.4 定义框架实现 在使端项中创建配置配置件 创建 sqlMapConfig.xml mapper.xml User实体 〈configuration〉 select * from user where id = #{id} and username =#{username}select * from userpublic class User { //主键标识 private Integer id; //户名 private String username; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } @Override public String toString() { return “User{” + “id=” + id + 再创建个Maven程并且导需要到的依赖坐标 “, username='” + username + ‘\” + ‘}’; } } UTF-8 UTF-8 1.8 1.8 1.8 mysql mysql-connector-java 5.1.17 c3p0 c3p0 0.9.1.2 log4j log4j 1.2.12 junit junit 4.10 dom4j dom4j 1.6.1 jaxen jaxen 1.1.6 Configuration MappedStatement public class Configuration { //数据源 private DataSource dataSource; //map集合: key:statementId value:MappedStatement private Map<string,mappedstatement> mappedStatementMap = new HashMap<string, mappedstatement=””>(); public DataSource getDataSource() { return dataSource; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } public Map<string, mappedstatement=””> getMappedStatementMap() { return mappedStatementMap; } public void setMappedStatementMap(Map<string, mappedstatement=””> mappedStatementMap) { this.mappedStatementMap = mappedStatementMap; } } public class MappedStatement { //id private Integer id; //sql语句 private String sql; //输参数 private Class paramterType; //输出参数 private Class resultType; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getSql() { return sql; } public void setSql(String sql) { this.sql = sql; Resources SqlSessionFactoryBuilder XMLConfigerBuilder } 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; } } public class Resources { public static InputStream getResourceAsSteam(String path){ InputStream resourceAsStream = Resources.class.getClassLoader.getResourceAsStream(path); return resourceAsStream; } } public class SqlSessionFactoryBuilder { private Configuration configuration; public SqlSessionFactoryBuilder() { this.configuration = new Configuration(); } public SqlSessionFactory build(InputStream inputStream) throws DocumentException, PropertyVetoException, ClassNotFoundException { //1.解析配置件,封装Configuration XMLConfigerBuilder xmlConfigerBuilder = new XMLConfigerBuilder(configuration); Configuration configuration = xmlConfigerBuilder.parseConfiguration(inputStream); //2.创建 sqlSessionFactory SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration); return sqlSessionFactory; } public class XMLConfigerBuilder { XMLMapperBuilder private Configuration configuration; public XMLConfigerBuilder(Configuration configuration) { this.configuration = new Configuration(); } public Configuration parseConfiguration(InputStream inputStream) throws DocumentException, PropertyVetoException, ClassNotFoundException { Document document = new SAXReader().read(inputStream); // Element rootElement = document.getRootElement(); List propertyElements = rootElement.selectNodes(“//property”); Properties properties = new Properties(); for (Element propertyElement : propertyElements) { String name = propertyElement.attributeValue(“name”); String value = propertyElement.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 configuration.setDataSource(comboPooledDataSource); //mapper 部分 List mapperElements = rootElement.selectNodes(“//mapper”); XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration); for (Element mapperElement : mapperElements) { String mapperPath = mapperElement.attributeValue(“resource”); InputStream resourceAsSteam = Resources.getResourceAsSteam(mapperPath); xmlMapperBuilder.parse(resourceAsSteam); } return configuration; } public class XMLMapperBuilder { private Configuration configuration; public XMLMapperBuilder(Configuration configuration) { sqlSessionFactory 接及D efaultSqlSessionFactory 实现类 this.configuration = configuration; } public void parse(InputStream inputStream) throws DocumentException, ClassNotFoundException { Document document = new SAXReader().read(inputStream); Element rootElement = document.getRootElement(); String namespace = rootElement.attributeValue(“namespace”); List select = rootElement.selectNodes(“select”); for (Element element : select) { //id的值 String id = element.attributeValue(“id”); String paramterType = element.attributeValue(“paramterType”); String resultType = element.attributeValue(“resultType”); //输参 数class Class paramterTypeClass = getClassType(paramterType); //返回结果class Class resultTypeClass = getClassType(resultType); //statementId String key = namespace + “.” + id; //sql语句 String textTrim = element.getTextTrim(); //封装 mappedStatement MappedStatement mappedStatement = new MappedStatement(); mappedStatement.setId(id); mappedStatement.setParamterType(paramterTypeClass); mappedStatement.setResultType(resultTypeClass); mappedStatement.setSql(textTrim); //填充 configuration configuration.getMappedStatementMap().put(key, mappedStatement); private Class getClassType (String paramterType) throws ClassNotFoundException { Class aClass = Class.forName(paramterType); return aClass; } } sqlSession 接及 DefaultSqlSession 实现类 public interface SqlSessionFactory { public SqlSession openSession(); } public class DefaultSqlSessionFactory implements SqlSessionFactory { private Configuration configuration; public DefaultSqlSessionFactory(Configuration configuration) { this.configuration = configuration; } public SqlSession openSession(){ return new DefaultSqlSession(configuration); } } public interface SqlSession { public List selectList(String statementId, Object… param) Exception; public T selectOne(String statementId,Object… params) throws Exception; public void close() throws SQLException; } public class DefaultSqlSession implements SqlSession { private Configuration configuration; public DefaultSqlSession(Configuration configuration) { this.configuration = configuration; //处理器对象 private Executor simpleExcutor = new SimpleExecutor(); public List < E > selectList(String statementId, Object…param) throws Exception { MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId); List query = simpleExcutor.query(configuration, mappedStatement, param); return query; } //selectOne 中调 selectList public T selectOne(String statementId, Object…params) throws Exception { List objects = selectList(statementId, params); if (objects.size() == 1) { return (T) objects.get(0); } else { throw new RuntimeException(“返回结果过多”); } Executor SimpleExecutor } public void close () throws SQLException { simpleExcutor.close(); } } public interface Executor { List query(Configuration configuration, MappedStatement mappedStatement,Object[] param) throws Exception; void close() throws SQLException; } public class SimpleExecutor implements Executor { private Connection connection = null; public List query(Configuration configuration, MappedStatement mappedStatement, Object[] param) throws SQLException, NoSuchFieldException, IllegalAccessException, InstantiationException, IntrospectionException, InvocationTargetException { //获取连接 connection = configuration.getDataSource().getConnection(); // select * from user where id = #{id} and username = #{username} String sql = mappedStatement.getSql(); //对sql进处理 BoundSql boundsql = getBoundSql(sql); // select * from where id = ? and username = ? String finalSql = boundsql.getSqlText(); //获取传参数类型 Class paramterType = mappedStatement.getParamterType(); //获取预编译preparedStatement对象 PreparedStatement preparedStatement = connection.prepareStatement(finalSql); List parameterMappingList = boundsql.getParameterMappingList(); for (int i = 0; i < parameterMappingList.size(); i++) { ParameterMapping parameterMapping = parameterMappingList.get(i); String name = parameterMapping.getName(); //反射 Field declaredField = paramterType.getDeclaredField(name); declaredField.setAccessible(true); //参数的值 Object o = declaredField.get(param[0]); //给占位符赋值 preparedStatement.setObject(i + 1, o); } ResultSet resultSet = preparedStatement.executeQuery(); Class resultType = mappedStatement.getResultType(); ArrayList results = new ArrayList(); while (resultSet.next()) { ResultSetMetaData metaData = resultSet.getMetaData(); (E) resultType.newInstance(); int columnCount = metaData.getColumnCount(); for (int i = 1; i <= columnCount; i++) { //属性名 String columnName = metaData.getColumnName(i); //属性值 Object value = resultSet.getObject(columnName); //创建属性描述器,为属性成读写法 PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultType); //获取写法 Method writeMethod = propertyDescriptor.getWriteMethod(); //向类中写值 writeMethod.invoke(o, value); } results.add(o); } return results; } @Override public void close() throws SQLException { connection.close(); } private BoundSql getBoundSql(String sql) { //标记处理类:主要是配合通标记解析器GenericTokenParser类完成对配置件等的解 析作,其中TokenHandler主要完成处理 ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler(); //GenericTokenParser :通的标记解析器,完成了代码段中的占位符的解析,然后再根 据给定的标记处理器(TokenHandler)来进表达式的处理 //三个参数:分别为openToken (开始标记)、closeToken (结束标记)、handler (标记 处 理器) GenericTokenParser genericTokenParser = new GenericTokenParser(“# {“, “}”, parameterMappingTokenHandler); String parse = genericTokenParser.parse(sql); List parameterMappings = parameterMappingTokenHandler.getParameterMappings(); BoundSql boundSql = new BoundSql(parse, parameterMappings); return boundSql; BoundSql 1.5 定义框架优化 通过上述我们的定义框架,我们解决了JDBC操作数据库带来的些问题:例如频繁创建释放数据库连 接,硬编码,动封装返回结果集等问题,但是现在我们继续来分析刚刚完成的定义框架代码,有没 有什么问题? 问题如下: dao的实现类中存在重复的代码,整个操作的过程模板重复(创建sqlsession,调sqlsession 法,关闭 sqlsession) dao的实现类中存在硬编码,调sqlsession的法时,参数statement的id硬编码 } } public class BoundSql { //解析过后的sql语句 private String sqlText; //解析出来的参数 private List parameterMappingList = new ArrayList(); public BoundSql(String sqlText, List parameterMappingList) { this.sqlText = sqlText; this.parameterMappingList = parameterMappingList; } public String getSqlText() { return sqlText; } public void setSqlText(String sqlText) { this.sqlText = sqlText; } public List getParameterMappingList() { return parameterMappingList; } public void setParameterMappingList(List parameterMappingList) { this.parameterMappingList = parameterMappingList; } } 解决:使代理模式来创建接的代理对象 在sqlSession中添加法 实现类 @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); Systemout.println(userl); } public interface SqlSession { public T getMappper(Class mapperClass); @Override public 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); } }); 第部分:Mybatis相关概念 2.1 对象/关系数据库映射(ORM) ORM全称Object/Relation Mapping:表示对象-关系映射的缩写 ORM完成向对象的编程语到关系数据库的映射。当ORM框架完成映射后,程序员既可以利向 对象程序设计语的简单易性,可以利关系数据库的技术优势。ORM把关系数据库包装成向对 象的模型。ORM框架是向对象设计语与关系数据库发展不同步时的中间解决案。采ORM框架 后,应程序不再直接访问底层数据库,是以向对象的式来操作持久化对象,ORM框架则将这 些向对象的操作转换成底层SQL操作。ORM框架实现的效果:把对持久化对象的保存、修改、删除 等操作,转换为对数据库的操作 2.2 Mybatis简介 MyBatis是款优秀的基于ORM的半动轻量级持久层框架,它持定制化SQL、存储过程以及级映 射。MyBatis避免了乎所有的JDBC代码和动设置参数以及获取结果集。MyBatis可以使简单的 XML或注解来配置和映射原类型、接和Java的POJO (Plain Old Java Objects,普通式Java对 象) 为数据库中的记录。 2.3 Mybatis历史 原是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 Mybatis优势 Mybatis是个半动化的持久层框架,对开发员开说,核sql还是需要进优化,sql和java编 码进分离,功能边界清晰,个专注业务,个专注数据。 分析图示如下: return o; } 第三部分:Mybatis基本应 3.1 快速 MyBatis官地址:http://www.mybatis.org/mybatis-3/ 3.1.1 开发步骤: ①添加MyBatis的坐标 ②创建user数据表 ③编写User实体类 ④编写映射件UserMapper.xml ⑤编写核件SqlMapConfig.xml ⑥编写测试类 3.1.1 环境搭建: 1)导MyBatis的坐标和其他相关坐标 2) 创建user数据表 3) 编写User实体 UTF-8 UTF-8 1.8 1.8 1.8 org.mybatis mybatis 3.4.5 mysql mysql-connector-java 5.1.6 runtime junit junit 4.12 test log4j log4j 1.2.12 4)编写UserMapper映射件 5) 编写MyBatis核件 6) 编写测试代码 public class User { private int id; private String username; private String password; //省略get个set法 } select * from User3.1.4 MyBatis的增删改查操作 MyBatis的插数据操作 1)编写UserMapper映射件 2)编写插实体User的代码 3)插操作注意问题 插语句使insert标签 在映射件中使parameterType属性指定要插的数据类型 Sql语句中使#{实体属性名}式引实体中的属性值 //加载核配置件 InputStream resourceAsStream = Resources.getResourceAsStream(“SqlMapConfig.xml”); //获得sqlSession对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //获得sqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); //执sql语句 List userList = sqlSession.selectList(“userMapper.findAll”); //打印结果 System.out.println(userList); //释放资源 sqlSession.close(); insert into user values(#{id},#{username},#{password}) InputStream resourceAsStream = Resources.getResourceAsStream(“SqlMapConfig.xml”); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); int insert = sqlSession.insert(“userMapper.add”, user); System.out.println(insert); //提交事务 sqlSession.commit(); sqlSession.close(); 插操作使的API是sqlSession.insert(“命名空间.id”,实体对象); 插操作涉及数据库数据变化,所以要使sqlSession对象显示的提交事务,即sqlSession.commit() 3.1.5 MyBatis的修改数据操作 1)编写UserMapper映射件 2)编写修改实体User的代码 3)修改操作注意问题 修改语句使update标签 修改操作使的API是sqlSession.update(“命名空间.id”,实体对象); 3.1.6 MyBatis的删除数据操作 1)编写UserMapper映射件 2)编写删除数据的代码 update user set username=#{username},password=#{password} where id=# {id} InputStream resourceAsStream = Resources.getResourceAsStream(“SqlMapConfig.xml”); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); int update = sqlSession.update(“userMapper.update”, user); System.out.println(update); sqlSession.commit(); sqlSession.close(); delete from user where id=#{id} 3)删除操作注意问题 删除语句使delete标签 Sql语句中使#{任意字符串}式引传递的单个参数 删除操作使的API是sqlSession.delete(“命名空间.id”,Object); 3.1.5 MyBatis的映射件概述 3.1.6 核配置件分析: MyBatis核配置件层级关系 InputStream resourceAsStream = Resources.getResourceAsStream(“SqlMapConfig.xml”); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); int delete = sqlSession.delete(“userMapper.delete”,3); System.out.println(delete); sqlSession.commit(); sqlSession.close(); MyBatis常配置解析 1)environments标签 数据库环境的配置,持多环境配置 其中,事务管理器(transactionManager)类型有两种: JDBC:这个配置就是直接使了JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作 域。 MANAGED:这个配置乎没做什么。它从来不提交或回滚个连接,是让容器来管理事务的整个 命周期(如 JEE 应服务器的上下)。 默认情况下它会关闭连接,然些容器并不希望这样,因 此需要将 closeConnection 属性设置为 false 来阻它默认的关闭为。 其中,数据源(dataSource)类型有三种: UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。 POOLED:这种数据源的实现利“池”的概念将 JDBC 连接对象组织起来。 JNDI:这个数据源的实现是为了能在如 EJB 或应服务器这类容器中使,容器可以集中或在外部配 置数据源,然后放置个 JNDI 上下的引。 2)mapper标签 该标签的作是加载映射的,加载式有如下种: 3.1.7 Mybatis相应API介绍 SqlSession构建器SqlSessionFactoryBuilder 常API:SqlSessionFactory build(InputStream inputStream) 通过加载mybatis的核件的输流的形式构建个SqlSessionFactory对象 其中, Resources 具类,这个类在 org.apache.ibatis.io 包中。Resources 类帮助你从类路径下、 件系统或个 web URL 中加载资源件。 SqlSession对象SqlSessionFactory SqlSessionFactory 有多个个法创建SqlSession 实例。常的有如下两个: 使相对于类路径的资源引,例如: 使完全限定资源定位符(URL),例如: 使映射器接实现类的完全限定类名,例如: 将包内的映射器接实现全部注册为映射器,例如: String resource = “org/mybatis/builder/mybatis-config.xml”; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(inputStream); SqlSession会话对象 SqlSession 实例在 MyBatis 中是常强的个类。在这你会看到所有执语句、提交或回滚事务 和获取映射器实例的法。 执语句的法主要有: 操作事务的法主要有: 3.2 Mybatis的Dao层实现 3.2.1 传统开发式 编写UserDao接 编写UserDaoImpl实现 T selectOne(String statement, Object parameter) List 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() public interface UserDao { List findAll() throws IOException; } 测试传统式 3.2.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的类型相同 编写UserMapper接 public class UserDaoImpl implements UserDao { public List findAll() throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream(“SqlMapConfig.xml”); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); List userList = sqlSession.selectList(“userMapper.findAll”); sqlSession.close(); return userList; } } @Test public void testTraditionDao() throws IOException { UserDao userDao = new UserDaoImpl(); List all = userDao.findAll(); System.out.println(all); } 测试代理式 第四部分:Mybatis配置件深 4.1 核配置件SqlMapConfig.xml 4.1.1 MyBatis核配置件层级关系 @Test public void testProxyDao() throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream(“SqlMapConfig.xml”); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); //获得MyBatis框架成的UserMapper接的实现类 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = userMapper.findById(1); System.out.println(user); sqlSession.close(); } 4.2 MyBatis常配置解析 1)environments标签 数据库环境的配置,持多环境配置 其中,事务管理器(transactionManager)类型有两种: JDBC:这个配置就是直接使了JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作 域。 MANAGED:这个配置乎没做什么。它从来不提交或回滚个连接,是让容器来管理事务的整个 命周期(如 JEE 应服务器的上下)。 默认情况下它会关闭连接,然些容器并不希望这样,因 此需要将 closeConnection 属性设置为 false 来阻它默认的关闭为。 其中,数据源(dataSource)类型有三种: UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。 POOLED:这种数据源的实现利“池”的概念将 JDBC 连接对象组织起来。 JNDI:这个数据源的实现是为了能在如 EJB 或应服务器这类容器中使,容器可以集中或在外部配 置数据源,然后放置个 JNDI 上下的引。 2)mapper标签 该标签的作是加载映射的,加载式有如下种: 3)Properties标签 实际开发中,习惯将数据源的配置信息单独抽取成个properties件,该标签可以加载额外配置的 properties件 4)typeAliases标签 类型别名是为Java 类型设置个短的名字。原来的类型名称配置如下 使相对于类路径的资源引,例如: 使完全限定资源定位符(URL),例如: 使映射器接实现类的完全限定类名,例如: 将包内的映射器接实现全部注册为映射器,例如: 配置typeAliases,为com.lagou.domain.User定义别名为user 上我们是定义的别名,mybatis框架已经为我们设置好的些常的类型的别名 4.2 映射配置件mapper.xml 动态sql语句 动态sql语句概述 Mybatis 的映射件中,前我们的 SQL 都是较简单的,有些时候业务逻辑复杂时,我们的 SQL是 动态变化的,此时在前的学习中我们的 SQL 就不能满要求了。 参考的官档,描述如下: 动态 SQL 之 我们根据实体类的不同取值,使不同的 SQL语句来进查询。如在 id如果不为空时可以根据id查 询,如果username 不同空时还要加户名作为条件。这种情况在我们的多条件组合查询中经常会碰 到。 当查询条件id和username都存在时,控制台打印的sql语句如下: 当查询条件只有id存在时,控制台打印的sql语句如下:select * from User and id=#{id} and username=#{username}… … … //获得MyBatis框架成的UserMapper接的实现类 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User condition = new User(); condition.setId(1); condition.setUsername(“lucy”); User user = userMapper.findByCondition(condition); … … … … … … //获得MyBatis框架成的UserMapper接的实现类 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User condition = new User(); condition.setId(1); User user = userMapper.findByCondition(condition); … … … 动态 SQL 之 循环执sql的拼接操作,例如:SELECT * FROM USER WHERE id IN (1,2,5)。 测试代码段如下: foreach标签的属性含义如下: 标签于遍历集合,它的属性: collection:代表要遍历的集合元素,注意编写时不要写#{} open:代表语句的开始部分 close:代表结束部分 item:代表遍历集合的每个元素,成的变量名 sperator:代表分隔符select * from User #{id}… … … //获得MyBatis框架成的UserMapper接的实现类 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); int[] ids = new int[]{2,5}; List userList = userMapper.findByIds(ids); System.out.println(userList); … … … SQL段抽取 Sql 中可将重复的 sql 提取出来,使时 include 引即可,最终达到 sql 重的的 第五部分:Mybatis复杂映射开发 5.1 对查询 5.1.1 对查询的模型 户表和订单表的关系为,个户有多个订单,个订单只从属于个户 对查询的需求:查询个订单,与此同时查询出该订单所属的户 5.1.2对查询的语句 对应的sql语句:select * from orders o,user u where o.uid=u.id; 查询的结果如下: where id=#{id}#{id}5.1.3 创建Order和User实体 5.1.4 创建OrderMapper接 5.1.5 配置OrderMapper.xml public class Order { private int id; private Date ordertime; private double total; //代表当前订单从属于哪个客户 private User user; } public class User { private int id; private String username; private String password; private Date birthday; } public interface OrderMapper { List findAll(); }select * from orders o,user u where o.uid=u.id其中还可以配置如下: 5.1.6 测试结果 5.2 对多查询 5.2.1 对多查询的模型 户表和订单表的关系为,个户有多个订单,个订单只从属于个户 对多查询的需求:查询个户,与此同时查询出该户具有的订单 5.2.2 对多查询的语句 对应的sql语句:select *,o.id oid from user u left join orders o on u.id=o.uid; 查询的结果如下: OrderMapper mapper = sqlSession.getMapper(OrderMapper.class); List all = mapper.findAll(); for(Order order : all){ System.out.println(order); } 5.2.3 修改User实体 5.2.4 创建UserMapper接 5.2.5 配置UserMapper.xml public class Order { private int id; private Date ordertime; private double total; //代表当前订单从属于哪个客户 private User user; } public class User { private int id; private String username; private String password; private Date birthday; //代表当前户具备哪些订单 private List orderList; } public interface UserMapper { List findAll(); } 5.2.6 测试结果 5.3 多对多查询 5.3.1 多对多查询的模型 户表和表的关系为,个户有多个,个被多个户使 多对多查询的需求:查询户同时查询出该户的所有select *,o.id oid from user u left join orders o on u.id=o.uidUserMapper mapper = sqlSession.getMapper(UserMapper.class); List all = mapper.findAll(); for(User user : all){ System.out.println(user.getUsername()); List orderList = user.getOrderList(); for(Order order : orderList){ System.out.println(order); } System.out.println(“———————————-“); } 5.3.2 多对多查询的语句 对应的sql语句:select u.,r.,r.id rid from user u left join user_role ur on u.id=ur.user_id inner join role r on ur.role_id=r.id; 查询的结果如下: 5.3.3 创建Role实体,修改User实体 5.3.4 添加UserMapper接法 5.3.5 配置UserMapper.xml public class User { private int id; private String username; private String password; private Date birthday; //代表当前户具备哪些订单 private List orderList; //代表当前户具备哪些 private List roleList; } public class Role { private int id; private String rolename; } List findAllUserAndRole(); 5.3.6 测试结果 5.4 知识结 MyBatis多表配置式: 对配置:使做配置 对多配置:使+做配置 多对多配置:使+做配置 第六部分:Mybatis注解开发 6.1 MyBatis的常注解select u.*,r.*,r.id rid from user u left join user_role ur on u.id=ur.user_id inner join role r on ur.role_id=r.idUserMapper mapper = sqlSession.getMapper(UserMapper.class); List all = mapper.findAllUserAndRole(); for(User user : all){ System.out.println(user.getUsername()); List roleList = user.getRoleList(); for(Role role : roleList){ System.out.println(role); } System.out.println(“———————————-“); } 这年来注解开发越来越流,Mybatis也可以使注解开发式,这样我们就可以减少编写Mapper 映射件了。我们先围绕些基本的CRUD来学习,再学习复杂映射多表操作。 @Insert:实现新增 @Update:实现更新 @Delete:实现删除 @Select:实现查询 @Result:实现结果集封装 @Results:可以与@Result 起使,封装多个结果集 @One:实现对结果集封装 @Many:实现对多结果集封装 6.2 MyBatis的增删改查 我们完成简单的user表的增删改查的操作 private UserMapper userMapper; @Before public void before() throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream(“SqlMapConfig.xml”); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(true); userMapper = sqlSession.getMapper(UserMapper.class); } @Test public void testAdd() { User user = new User(); user.setUsername(“测试数据”); user.setPassword(“123”); user.setBirthday(new Date()); userMapper.add(user); } @Test public void testUpdate() throws IOException { User user = new User(); user.setId(16); user.setUsername(“测试数据修改”); user.setPassword(“abc”); user.setBirthday(new Date()); userMapper.update(user); } 修改MyBatis的核配置件,我们使了注解替代的映射件,所以我们只需要加载使了注解的 Mapper接即可 或者指定扫描包含映射关系的接所在的包也可以 6.3 MyBatis的注解实现复杂映射开发 实现复杂关系映射之前我们可以在映射件中通过配置来实现,使注解开发后,我们可以使 @Results注解,@Result注解,@One注解,@Many注解组合完成复杂关系的配置 @Test public void testDelete() throws IOException { userMapper.delete(16); } @Test public void testFindById() throws IOException { User user = userMapper.findById(1); System.out.println(user); } @Test public void testFindAll() throws IOException { List all = userMapper.findAll(); for(User user : all){ System.out.println(user); } } 6.4 对查询 6.4.1 对查询的模型 户表和订单表的关系为,个户有多个订单,个订单只从属于个户 对查询的需求:查询个订单,与此同时查询出该订单所属的户 6.4.2 对查询的语句 对应的sql语句: 查询的结果如下: select * from orders; select * from user where id=查询出订单的uid; 6.4.3 创建Order和User实体 6.4.4 创建OrderMapper接 6.4.5 使注解配置Mapper public class Order { private int id; private Date ordertime; private double total; //代表当前订单从属于哪个客户 private User user; } public class User { private int id; private String username; private String password; private Date birthday; } public interface OrderMapper { List findAll(); } 6.4.6 测试结果 6.5 对多查询 6.5.1 对多查询的模型 户表和订单表的关系为,个户有多个订单,个订单只从属于个户 对多查询的需求:查询个户,与此同时查询出该户具有的订单 public interface OrderMapper { @Select(“select * from orders”) @Results({ @Result(id=true,property = “id”,column = “id”), @Result(property = “ordertime”,column = “ordertime”), @Result(property = “total”,column = “total”), @Result(property = “user”,column = “uid”, javaType = User.class, one = @One(select = “com.lagou.mapper.UserMapper.findById”)) }) List findAll(); } public interface UserMapper { @Select(“select * from user where id=#{id}”) User findById(int id); } @Test public void testSelectOrderAndUser() { List all = orderMapper.findAll(); for(Order order : all){ System.out.println(order); } } 6.5.2 对多查询的语句 对应的sql语句: 查询的结果如下: 6.5.3 修改User实体 6.5.4 创建UserMapper接 select * from user; select * from orders where uid=查询出户的id; public class Order { private int id; private Date ordertime; private double total; //代表当前订单从属于哪个客户 private User user; } public class User { private int id; private String username; private String password; private Date birthday; //代表当前户具备哪些订单 private List orderList; } 6.5.5 使注解配置Mapper 6.5.6 测试结果 6.6 多对多查询 List findAllUserAndOrder(); public interface UserMapper { @Select(“select * from user”) @Results({ @Result(id = true,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.lagou.mapper.OrderMapper.findByUid”)) }) List findAllUserAndOrder(); } public interface OrderMapper { @Select(“select * from orders where uid=#{uid}”) List findByUid(int uid); } List all = userMapper.findAllUserAndOrder(); for(User user : all){ System.out.println(user.getUsername()); List orderList = user.getOrderList(); for(Order order : orderList){ System.out.println(order); } System.out.println(“—————————–“); } 6.6.1 多对多查询的模型 户表和表的关系为,个户有多个,个被多个户使 多对多查询的需求:查询户同时查询出该户的所有 6.6.2 多对多查询的语句 对应的sql语句: 查询的结果如下: 6.6.3 创建Role实体,修改User实体 select * from user; select * from role r,user_role ur where r.id=ur.role_id and ur.user_id=户的id public class User { private int id; private String username; private String password; private Date birthday; //代表当前户具备哪些订单 private List orderList; //代表当前户具备哪些 private List roleList; } public class Role { private int id; private String rolename; 6.6.4 添加UserMapper接法 6.6.5 使注解配置Mapper 6.6.6 测试结果 } List findAllUserAndRole(); public interface UserMapper { @Select(“select * from user”) @Results({ @Result(id = true,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.lagou.mapper.RoleMapper.findByUid”)) }) List findAllUserAndRole();} public interface RoleMapper { @Select(“select * from role r,user_role ur where r.id=ur.role_id and ur.user_id=#{uid}”) List findByUid(int uid); } UserMapper mapper = sqlSession.getMapper(UserMapper.class); List all = mapper.findAllUserAndRole(); for(User user : all){ System.out.println(user.getUsername()); List roleList = user.getRoleList(); for(Role role : roleList){ System.out.println(role); } System.out.println(“———————————-“); } 第七部分:Mybatis缓存 7.1 级缓存 ①、在个sqlSession中,对User表根据id进两次查询,查看他们发出sql语句的情况 查看控制台打印情况: @Test public void test1(){ //根据 sqlSessionFactory 产 session SqlSession sqlSession = sessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //第次查询,发出sql语句,并将查询出来的结果放进缓存中 User u1 = userMapper.selectUserByUserId(1); System.out.println(u1); //第次查询,由于是同个sqlSession,会在缓存中查询结果 //如果有,则直接从缓存中取出来,不和数据库进交互 User u2 = userMapper.selectUserByUserId(1); System.out.println(u2); sqlSession.close(); } ② 、同样是对user表进两次查询,只不过两次查询之间进了次update操作。 查看控制台打印情况: ③、总结 1、第次发起查询户id为1的户信息,先去找缓存中是否有id为1的户信息,如果没有,从 数据 库查询户信息。得到户信息,将户信息存储到级缓存中。 2、 如果中间sqlSession去执commit操作(执插、更新、删除),则会清空SqlSession中的 级缓存,这样做的的为了让缓存中存储的是最新的信息,避免脏读。 3、 第次发起查询户id为1的户信息,先去找缓存中是否有id为1的户信息,缓存中有,直 接从 缓存中获取户信息 @Test public void test2(){ //根据 sqlSessionFactory 产 session SqlSession sqlSession = sessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //第次查询,发出sql语句,并将查询的结果放缓存中 User u1 = userMapper.selectUserByUserId( 1 ); System.out.println(u1); //第步进了次更新操作,sqlSession.commit() u1.setSex(“”); userMapper.updateUserByUserId(u1); sqlSession.commit(); //第次查询,由于是同个sqlSession.commit(),会清空缓存信息 //则此次查询也会发出sql语句 User u2 = userMapper.selectUserByUserId(1); System.out.println(u2); 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(); 创建缓存key会经过系列的update法,udate法由个CacheKey这个对象来执的,这个 update法最终由updateList的list来把五个值存进去,对照上的代码和下的图示,你应该能 理解 这五个值都是什么了 这需要注意下最后个值,configuration.getEnvironment().getId()这是什么,这其实就是 定义在 mybatis-config.xml中的标签,如下。 //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()); } 那么我们回归正题,那么创建完缓存之后该在何处呢?总不会凭空创建个缓存不使吧?绝对不会 的,经过我们对级缓存的探究之后,我们发现级缓存更多是于查询操作,毕竟级缓存也叫做查 询缓存吧,为什么叫查询缓存我们会说。我们先来看下这个缓存到底在哪了,我们跟踪到 query法如下: Override public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameter); //创建缓存 CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); } @SuppressWarnings(“unchecked”) Override public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { … list = resultHandler == null ? (List) localCache.getObject(key) : null; if (list != null) { //这个主要是处理存储过程的。 handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } … } // queryFromDatabase 法 private List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List list; 如果查不到的话,就从数据库查,在queryFromDatabase中,会对localcache进写。 localcache 对象的put法最终交给Map进存放 7.2 级缓存 级缓存的原理和级缓存原理样,第次查询,会将数据放缓存中,然后第次查询则会直接去 缓存中取。但是级缓存是基于sqlSession的,级缓存是基于mapper件的namespace的,也 就 是说多个sqlSession可以共享个mapper中的级缓存区域,并且如果两个mapper的namespace 相 同,即使是两个mapper,那么这两个mapper中执sql查询到的数据也将存在相同的级缓存区域 中 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; } private Map<object, object=””> cache = new HashMap<object, object=””>(); @Override public void putObject(Object key, Object value) { cache.put(key, value); } 如何使级缓存 ① 、开启级缓存 和级缓存默认开启不样,级缓存需要我们动开启 先在全局配置件sqlMapConfig.xml件中加如下代码: 其次在UserMapper.xml件中开启缓存 我们可以看到mapper.xml件中就这么个空标签,其实这可以配置,PerpetualCache这个类是 mybatis默认实现缓存功能的类。我们不写type就使mybatis默认的缓存,也可以去实现Cache接 来定义缓存。 我们可以看到级缓存底层还是HashMap结构 开启了级缓存后,还需要将要缓存的pojo实现Serializable接,为了将缓存数据取出执反序列化操 作,因为级缓存数据存储介质多种多样,不定只存在内存中,有可能存在硬盘中,如果我们要再取 这个缓存的话,就需要反序列化了。所以mybatis中的pojo都去实现Serializable接 ③、测试 、测试级缓存和sqlSession关 public class PerpetualCache implements Cache { private final String id; private MapcObject, Object> cache = new HashMapC); public PerpetualCache(St ring id) { this.id = id; } public class User implements Serializable( //户ID private int id; //户姓名 private String username; //户性别 private String sex; } @Test 可以看出上两个不同的sqlSession,第个关闭了,第次查询依然不发出sql查询语句 、测试执commit()操作,级缓存数据清空 查看控制台情况: public void testTwoCache(){ //根据 sqlSessionFactory 产 session SqlSession sqlSession1 = sessionFactory.openSession(); SqlSession sqlSession2 = sessionFactory.openSession(); UserMapper userMapper1 = sqlSession1.getMapper(UserMapper. class ); UserMapper userMapper2 = sqlSession2.getMapper(UserMapper. class ); //第次查询,发出sql语句,并将查询的结果放缓存中 User u1 = userMapper1.selectUserByUserId(1); System.out.println(u1); sqlSession1.close(); //第次查询完后关闭 sqlSession //第次查询,即使sqlSession1已经关闭了,这次查询依然不发出sql语句 User u2 = userMapper2.selectUserByUserId(1); System.out.println(u2); sqlSession2.close(); @Test public void testTwoCache(){ //根据 sqlSessionFactory 产 session SqlSession sqlSession1 = sessionFactory.openSession(); SqlSession sqlSession2 = sessionFactory.openSession(); SqlSession sqlSession3 = sessionFactory.openSession(); String statement = “com.lagou.pojo.UserMapper.selectUserByUserld” ; UserMapper userMapper1 = sqlSession1.getMapper(UserMapper. class ); UserMapper userMapper2 = sqlSession2.getMapper(UserMapper. class ); UserMapper userMapper3 = sqlSession2.getMapper(UserMapper. class ); //第次查询,发出sql语句,并将查询的结果放缓存中 User u1 = userMapperl.selectUserByUserId( 1 ); System.out.println(u1); sqlSessionl .close(); //第次查询完后关闭sqlSession //执更新操作,commit() u1.setUsername( “aaa” ); userMapper3.updateUserByUserId(u1); sqlSession3.commit(); //第次查询,由于上次更新操作,缓存数据已经清空(防数据脏读),这必须再次发出sql语 User u2 = userMapper2.selectUserByUserId( 1 ); System.out.println(u2); sqlSession2.close(); } ④、useCache和flushCache mybatis中还可以配置userCache和flushCache等配置项,userCache是来设置是否禁级缓 存 的,在statement中设置useCache=false可以禁当前select语句的级缓存,即每次查询都会发出 sql 去查询,默认情况是true,即该sql使级缓存 这种情况是针对每次查询都需要最新的数据sql,要设置成useCache=false,禁级缓存,直接从数 据 库中获取。 在mapper的同个namespace中,如果有其它insert、update, delete操作数据后需要刷新缓 存,如 果不执刷新缓存会出现脏读。 设置statement配置中的flushCache=”true”属性,默认情况下为true,即刷新缓存,如果改成false则 不 会刷新。使缓存时如果动修改数据库表中的查询数据会出现脏读。 般下执完commit操作都需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏 读。所以我们不设置,默认即可 7.3 级缓存整合redisselect * from user where id=#{id}select * from user where id=#{id}上我们介绍了 mybatis带的级缓存,但是这个缓存是单服务器作,法实现分布式缓存。 那么 什么是分布式缓存呢?假设现在有两个服务器1和2,户访问的时候访问了 1服务器,查询后的缓 存就 会放在1服务器上,假设现在有个户访问的是2服务器,那么他在2服务器上就法获取刚刚那个 缓 存,如下图所示: 为了解决这个问题,就得找个分布式的缓存,专来存储缓存数据的,这样不同的服务器要缓存数 据都往它那存,取缓存数据也从它那取,如下图所示: 如上图所示,在个不同的服务器之间,我们使第三缓存框架,将缓存都放在这个第三框架中, 然后论有多少台服务器,我们都能从缓存中获取数据。 这我们介绍mybatis与redis的整合。 刚刚提到过,mybatis提供了个eache接,如果要实现的缓存逻辑,实现cache接开发即可。 mybati s本身默认实现了个,但是这个缓存的实现法实现分布式缓存,所以我们要来实现。 redis分布式缓存就可以,mybatis提供了个针对cache接的redis实现类,该类存在mybatis-redis包 中 实现: 1. pom件 2.配置件 Mapper.xml 3.redis.properties 4.测试 org.mybatis.caches mybatis-redis 1.0.0-beta2 select * from userredis.host=localhost redis.port=6379 redis.connectionTimeout=5000 redis.password= redis.database=0 @Test public void SecondLevelCache(){ SqlSession sqlSession1 = sqlSessionFactory.openSession(); 源码分析: RedisCache和家普遍实现Mybatis的缓存案同异,是实现Cache接,并使jedis操作缓 存;不过该项在设计细节上有些区别; RedisCache在mybatis启动的时候,由MyBatis的CacheBuilder创建,创建的式很简单,就是调 RedisCache的带有String参数的构造法,即RedisCache(String id);在RedisCache的构造法中, 调了 RedisConfigu rationBuilder 来创建 RedisConfig 对象,并使 RedisConfig 来创建JedisPool。 RedisConfig类继承了 JedisPoolConfig,并提供了 host,port等属性的包装,简单看下RedisConfig的 属性: SqlSession sqlSession2 = sqlSessionFactory.openSession(); SqlSession sqlSession3 = sqlSessionFactory.openSession(); IUserMapper mapper1 = sqlSession1.getMapper(IUserMapper.class); lUserMapper mapper2 = sqlSession2.getMapper(lUserMapper.class); lUserMapper mapper3 = sqlSession3.getMapper(IUserMapper.class); User user1 = mapper1.findUserById(1); sqlSession1.close(); //清空级缓存 User user = new User(); user.setId(1); user.setUsername(“lisi”); mapper3.updateUser(user); sqlSession3.commit(); User user2 = mapper2.findUserById(1); System.out.println(user1==user2); } public final class RedisCache implements Cache { public RedisCache(final String id) { if (id == null) { throw new IllegalArgumentException(“Cache instances require anID”); } 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()); } RedisConfig对象是由RedisConfigurationBuilder创建的,简单看下这个类的主要法: 核的法就是parseConfiguration法,该法从classpath中读取个redis.properties件: 并将该配置件中的内容设置到RedisConfig对象中,并返回;接下来,就是RedisCache使 RedisConfig类创建完成edisPool;在RedisCache中实现了个简单的模板法,来操作Redis: public class RedisConfig extends JedisPoolConfig { private String host = Protocol.DEFAULT_HOST; private int port = Protocol.DEFAULT_PORT; private int connectionTimeout = Protocol.DEFAULT_TIMEOUT; private int soTimeout = Protocol.DEFAULT_TIMEOUT; private String password; private int database = Protocol.DEFAULT_DATABASE; private String clientName; public RedisConfig parseConfiguration(ClassLoader classLoader) { Properties config = new Properties(); InputStream input = classLoader.getResourceAsStream(redisPropertiesFilename); if (input != null) { try { config.load(input); } catch (IOException e) { throw new RuntimeException( “An error occurred while reading classpath property ‘” + redisPropertiesFilename + “‘, see nested exceptions”, e); } finally { try { input.close(); } catch (IOException e) { // close quietly } } } RedisConfig jedisConfig = new RedisConfig(); setConfigProperties(config, jedisConfig); return jedisConfig; } host=localhost port=6379 connectionTimeout=5000 soTimeout=5000 password= database=0 clientName= 模板接为RedisCallback,这个接中就只需要实现了个doWithRedis法已: 接下来看看Cache中最重要的两个法:putObject和getObject,通过这两个法来查看mybatis-redis 储存数据的格式: 可以很清楚的看到,mybatis-redis在存储数据的时候,是使的hash结构,把cache的id作为这个hash 的key (cache的id在mybatis中就是mapper的namespace);这个mapper中的查询缓存数据作为 hash 的field,需要缓存的内容直接使SerializeUtil存储,SerializeUtil和其他的序列化类差不多,负责 对象 的序列化和反序列化; private Object execute(RedisCallback callback) { Jedis jedis = pool.getResource(); try { return callback.doWithRedis(jedis); } finally { jedis.close(); } } public interface RedisCallback { Object doWithRedis(Jedis jedis); } @Override public void putObject(final Object key, final Object value) { execute(new RedisCallback() { @Override public Object doWithRedis(Jedis jedis) { jedis.hset(id.toString().getBytes(), key.toString().getBytes(), SerializeUtil.serialize(value)); return null; } }); } @Override public Object getObject(final Object key) { return execute(new RedisCallback() { @Override public Object doWithRedis(Jedis jedis) { return SerializeUtil.unserialize(jedis.hget(id.toString().getBytes(), key.toString().getBytes())); } }); } 第部分: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 Mybatis插件原理 在四对象创建的时候 1、每个创建出来的对象不是直接返回的,是interceptorChain.pluginAll(parameterHandler); 2、获取到所有的Interceptor (拦截器)(插件需要实现的接);调 interceptor.plugin(target);返 回 target 包装后的对象 3、插件机制,我们可以使插件为标对象创建个代理对象;AOP (向切)我们的插件可 以 为四对象创建出代理对象,代理对象就可以拦截到四对象的每个执; 拦截 插件具体是如何拦截并附加额外的功能的呢?以ParameterHandler来说 interceptorChain保存了所有的拦截器(interceptors),是mybatis初始化的时候创建的。调拦截器链 中的拦截器依次的对标进拦截或增强。interceptor.plugin(target)中的target就可以理解为mybatis 中的四对象。返回的target是被重重代理后的对象 如果我们想要拦截Executor的query法,那么可以这样定义插件: 除此之外,我们还需将插件配置到sqlMapConfig.xm l中。 public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object object, BoundSql sql, InterceptorChain interceptorChain){ ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement,object,sql); parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); return parameterHandler; } public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; } @Intercepts({ @Signature( type = Executor.class, method = “query”, args= {MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class} ) }) public class ExeunplePlugin implements Interceptor { //省略逻辑 } 这样MyBatis在启动时可以加载插件,并保存插件实例到相关对象(InterceptorChain,拦截器链) 中。 待准备作做完后,MyBatis处于就绪状态。我们在执SQL时,需要先通过DefaultSqlSessionFactory 创建 SqlSession。Executor 实例会在创建 SqlSession 的过程中被创建, Executor实例创建完毕后, MyBatis会通过JDK动态代理为实例成代理类。这样,插件逻辑即可在 Executor相关法被调前执 。 以上就是MyBatis插件机制的基本原理 8.4 定义插件 8.4.1 插件接 Mybatis 插件接-Interceptor Intercept法,插件的核法 plugin法,成target的代理对象 setProperties法,传递插件所需参数 8.4.2定义插件 设计实现个定义插件 Intercepts ({//注意看这个花括号,也就这说这可以定义多个@Signature对多个地拦截,都 这个拦截器 @Signature (type = StatementHandler .class , //这是指拦截哪个接 method = “prepare”,//这个接内的哪个法名,不要拼错了 args = { Connection.class, Integer .class}),//// 这是拦截的法的参,按 顺序写到这,不要多也不要少,如果法重载,可是要通过法名和参来确定唯的 }) public class MyPlugin implements Interceptor { private final Logger logger = LoggerFactory.getLogger(this.getClass()); // //这是每次执操作的时候,都会进这个拦截器的法内 Override public Object intercept(Invocation invocation) throws Throwable { //增强逻辑 System.out.println(“对法进了增强….”); return invocation.proceed(); //执原法 } /** sqlMapConfig.xml mapper接 mapper.xml 测试类 * //主要是为了把这个拦截器成个代理放到拦截器链中 * ^Description包装标对象 为标对象创建代理对象 * @Param target为要拦截的对象 * @Return代理对象 */ Override public Object plugin(Object target) { System.out.println(“将要包装的标对象:”+target); return Plugin.wrap(target,this); } /**获取配置件的属性**/ //插件初始化的时候调,也只调次,插件配置的属性从这设置进来 Override public void setProperties(Properties properties) { System.out.println(“插件配置的初始化参数:”+properties ); } } public interface UserMapper { List selectUser(); }SELECT id,username FROM user8.5 源码分析 执插件逻辑 Plugin实现了 InvocationHandler接,因此它的invoke法会拦截所有的法调。invoke法会 对 所拦截的法进检测,以决定是否执插件逻辑。该法的逻辑如下: public class PluginTest { @Test public void test() throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream(“sqlMapConfig.xml”); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List byPaging = userMapper.selectUser(); for (User user : byPaging) { System.out.println(user); } } } // -Plugin public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { /* *获取被拦截法列表,如: * signatureMap.get(Executor.class), 可能返回 [query, update, commit] */ Set methods = signatureMap.get(method.getDeclaringClass()); //检测法列表是否包含被拦截的法 if (methods != null && methods.contains(method)) { //执插件逻辑 return interceptor.intercept(new Invocation(target, method, args)); //执被拦截的法 return method.invoke(target, args); } catch(Exception e){ } } invoke法的代码较少,逻辑不难理解。先,invoke法会检测被拦截法是否配置在插件的 @Signature注解中,若是,则执插件逻辑,否则执被拦截法。插件逻辑封装在intercept中,该 法的参数类型为Invocationo Invocation主要于存储标类,法以及法参数列表。下简单看 下该类的定义 关于插件的执逻辑就分析结束 8.6 pageHelper分插件 MyBati s可以使第三的插件来对功能进扩展,分助PageHelper是将分的复杂操作进封 装,使简单的式即可获得分的相关数据 开发步骤: ① 导通PageHelper的坐标 ② 在mybatis核配置件中配置PageHelper插件 ③ 测试分数据获取 ①导通PageHelper坐标 ② 在mybatis核配置件中配置PageHelper插件 public class Invocation { private final Object target; private final Method method; private final Object[] args; public Invocation(Object targetf Method method, Object[] args) { this.target = target; this.method = method; //省略部分代码 public Object proceed() throws InvocationTargetException, IllegalAccessException { //调被拦截的法 >> — com.github.pagehelper pagehelper 3.7.5 com.github.jsqlparser jsqlparser 0.9.1 ③ 测试分代码实现 获得分相关的其他参数 8.7 通 mapper 什么是通Mapper 通Mapper就是为了解决单表增删改查,基于Mybatis的插件机制。开发员不需要编写SQL,不需要 在DAO中增加法,只要写好实体类,就能持相应的增删改查法 如何使 1. 先在maven项,在pom.xml中引mapper的依赖 * @Test public void testPageHelper() { //设置分参数 PageHelper.startPage(1, 2); List select = userMapper2.select(null); for (User user : select) { System.out.println(user); } } } //其他分的数据 PageInfo pageInfo = new PageInfo(select); System.out.println(“总条数:”+pageInfo.getTotal()); System.out.println(“总数:”+pageInfo. getPages ()); System.out.println(“当前:”+pageInfo. getPageNum()); System.out.println(“每显万度:”+pageInfo.getPageSize()); System.out.println(“是否第:”+pageInfo.isIsFirstPage()); System.out.println(“是否最后:”+pageInfo.isIsLastPage()); tk.mybatis mapper 3.1.2 2. Mybatis配置件中完成配置 3. 实体类设置主键 4. 定义通mapper 5. 测试 @Table(name = “t_user”) public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String username; } import com.lagou.domain.User; import tk.mybatis.mapper.common.Mapper; public interface UserMapper extends Mapper { } public class UserTest { @Test public void test1() throws IOException { Inputstream resourceAsStream = Resources.getResourceAsStream(“sqlMapConfig.xml”); SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = build.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = new User(); user.setId(4); //(1)mapper基础接 //select 接 User user1 = userMapper.selectOne(user); //根据实体中的属性进查询,只能有 —个返回值 List users = userMapper.select(null); //查询全部结果 userMapper.selectByPrimaryKey(1); //根据主键字段进查询,法参数必须包含完 整的主键属性,查询条件使等号 userMapper.selectCount(user); //根据实体中的属性查询总数,查询条件使等号 // insert 接 int insert = userMapper.insert(user); //保存个实体,null值也会保存,不会使 数据库默认值 int i = userMapper.insertSelective(user); //保存实体,null的属性不会保存, 会使数据库默认值 // update 接 int i1 = userMapper.updateByPrimaryKey(user);//根据主键更新实体全部字段, null值会被更新 // delete 接 int delete = userMapper.delete(user); //根据实体属性作为条件进删除,查询条 件 使等号 userMapper.deleteByPrimaryKey(1); //根据主键字段进删除,法参数必须包含完 整的主键属性 //(2)example法 Example example = new Example(User.class); example.createCriteria().andEqualTo(“id”, 1); example.createCriteria().andLike(“val”, “1”); //定义查询 List users1 = userMapper.selectByExample(example); } } 第九部分:Mybatis架构原理 9.1架构设计 我们把Mybatis的功能架构分为三层: (1) API接层:提供给外部使的接 API,开发员通过这些本地API来操纵数据库。接层接收 到 调请求就会调数据处理层来完成具体的数据处理。 MyBatis和数据库的交互有两种式: a. 使传统的MyBati s提供的API ; b. 使Mapper代理的式 (2) 数据处理层:负责具体的SQL查找、SQL解析、SQL执和执结果映射处理等。它主要的的是根 据调的请求完成次数据库操作。 (3) 基础撑层:负责最基础的功能撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是 共 的东,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的撑 9.2主要构件及其相互关系 构件 描述 SqlSession 作为MyBatis作的主要顶层API,表示和数据库交互的会话,完成必要数 据库增删改查功能 Executor MyBatis执器,是MyBatis调度的核,负责SQL语句的成和查询缓 存的维护 StatementHandler 封装了JDBC Statement操作,负责对JDBC statement的操作,如设置参 数、将Statement结果集转换成List集合。 ParameterHandler 负责对户传递的参数转换成JDBC Statement所需要的参数, ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合; TypeHandler 负责java数据类型和jdbc数据类型之间的映射和转换 MappedStatement MappedStatement维护了条<select | update | delete | insert>节点 的封 装 SqlSource 负责根据户传递的parameterObject,动态地成SQL语句,将信息封 装到BoundSql对象中,并返回 BoundSql 表示动态成的SQL语句以及相应的参数信息 9.3总体流程 (1) 加载配置并初始化 触发条件:加载配置件 配置来源于两个地,个是配置件(主配置件conf.xml,mapper件*.xml),—个是java代码中的 注解,将主配置件内容解析封装到Configuration,将sql的配置信息加载成为个mappedstatement 对象,存储在内存之中 (2) 接收调请求 触发条件:调Mybatis提供的API 传参数:为SQL的ID和传参数对象 处理过程:将请求传递给下层的请求处理层进处理。 (3) 处理操作请求 触发条件:API接层传递请求过来 传参数:为SQL的ID和传参数对象 处理过程: (A) 根据SQL的ID查找对应的MappedStatement对象。 (B) 根据传参数对象解析MappedStatement对象,得到最终要执的SQL和执传参数。 (C) 获取数据库连接,根据得到的最终SQL语句和执传参数到数据库执,并得到执结果。 (D) 根据MappedStatement对象中的结果映射配置对得到的执结果进转换处理,并得到最终的处 理 结果。 (E) 释放连接资源。 (4) 返回处理结果 将最终的处理结果返回。 第部分:Mybatis源码剖析 10.1传统式源码剖析: 源码剖析-初始化 进源码分析: Inputstream inputstream = Resources.getResourceAsStream(“mybatisconfig.xml”); //这代码正是初始化作的开始。 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream); // 1.我们最初调的build public SqlSessionFactory build (InputStream inputStream){ //调了重载法 return build(inputStream, null, null); } MyBatis在初始化的时候,会将MyBatis的配置信息全部加载到内存中,使 org.apache.ibatis.session.Configuratio n 实例来维护 下进对配置件解析部分: 先对Configuration对象进介绍: // 2.调的重载法 public SqlSessionFactory build (InputStream inputStream, String environment, Properties properties){ try { // XMLConfigBuilder是专解析mybatis的配置件的类 XMLConfigBuilder parser = new XMLConfigBuilder(inputstream, environment, properties); //这调了个重载法。parser.parse()的返回值是Configuration对象 return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException(“Error building SqlSession.”, e) } Configuration对象的结构和xml配置件的对象乎相同。 回顾下xml中的配置标签有哪些: properties (属性),settings (设置),typeAliases (类型别名),typeHandlers (类型处理 器),objectFactory (对象),mappers (映射器)等 Configuration也有对应的对象属性来封 装它们 也就是说,初始化配置件信息的本质就是创建Configuration对象,将解析的xml数据封装到 Configuration内部属性中 /** * 解析 XML 成 Configuration 对象。 */ public Configuration parse () { //若已解析,抛出BuilderException异常 if (parsed) { throw new BuilderException(“Each XMLConfigBuilder can only be used once.”); } //标记已解析 parsed = true; // 解析 XML configuration 节点 parseConfiguration(parser.evalNode(“/configuration”)); 介绍下 MappedStatement : 作:MappedStatement与Mapper配置件中的个select/update/insert/delete节点相对应。 mapper中配置的标签都被封装到了此对象中,主要途是描述条SQL语句。 return configuration; } /** *解析XML */ private void parseConfiguration (XNode root){ try { //issue #117 read properties first // 解析 标签 propertiesElement(root.evalNode(“properties”)); // 解析〈settings /> 标签 Properties settings = settingsAsProperties(root.evalNode(“settings”)); //加载定义的VFS实现类 loadCustomVfs(settings); // 解析 标签 typeAliasesElement(root.evalNode(“typeAliases”)); //解析标签 pluginElement(root.evalNode(“plugins”)); // 解析 标签 objectFactoryElement(root.evalNode(“objectFactory”)); // 解析 标签 objectWrapperFactoryElement(root.evalNode(“objectWrapperFactory”)); // 解析 标签 reflectorFactoryElement(root.evalNode(“reflectorFactory”)); // 赋值 Configuration 属性 settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 // 解析〈environments /> 标签 environmentsElement(root.evalNode(“environments”)); // 解析 标签 databaseldProviderElement(root.evalNode(“databaseldProvider”)); // 解析 标签 typeHandlerElement(root.evalNode(“typeHandlers”)); //解析标签 mapperElement(root.evalNode(“mappers”)); } catch (Exception e) { throw new BuilderException(“Error parsing SQL Mapper Configuration.Cause:” + e, e); } } 初始化过程:回顾刚开 始介绍的加载配置件的过程中,会对mybatis-config.xm l中的各个标签都进 解析,其中有mappers 标签来引mapper.xml件或者配置mapper接的录。 样的个select标签会在初始化配置件时被解析封装成个MappedStatement对象,然后存储在 Configuration对象的mappedStatements属性中,mappedStatements 是个HashMap,存储时key =全限定类名+法名,value =对应的MappedStatement对象。 在configuration中对应的属性为 在 XMLConfigBuilder 中的处理: 到此对xml配置件的解析就结束了,回到步骤2.中调的重载build法 源码剖析-执SQL流程 先简单介绍SqlSession : SqlSession是个接,它有两个实现类:DefaultSqlSession (默认)和 SqlSessionManager (弃,不做介绍) SqlSession是MyBatis中于和数据库交互的顶层类,通常将它与ThreadLocal绑定,个会话使 个SqlSession,并且在使完毕后需要closeselect * from user where id=#{id}Map<string, mappedstatement=””> mappedStatements = new StrictMap (“Mapped Statements collection”) private void parseConfiguration(XNode root) { try { //省略其他标签的处理 mapperElement(root.evalNode(“mappers”)); } catch (Exception e) { throw new BuilderException(“Error parsing SQL Mapper Configuration. Cause:” + e, e); } } // 5.调的重载法 public SqlSessionFactory build(Configuration config) { //创建了 DefaultSqlSessionFactory 对象,传 Configuration 对象。 return new DefaultSqlSessionFactory(config); } SqlSession中的两个最重要的参数,configuration与初始化时的相同,Executor为执器 Executor: Executor也是个接,他有三个常的实现类: BatchExecutor (重语句并执批量更新) ReuseExecutor (重预处理语句 prepared statements) SimpleExecutor (普通的执器,默认) 继续分析,初始化完毕后,我们就要执SQL 了 获得 sqlSession public class DefaultSqlSession implements SqlSession { private final Configuration configuration; private final Executor executor; j SqlSession sqlSession = factory.openSession(); List list = sqlSession.selectList(“com.lagou.mapper.UserMapper.getUserByName”); //6. 进 o penSession 法。 public SqlSession openSession() { //getDefaultExecutorType()传递的是SimpleExecutor return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); } //7. 进penSessionFromDataSource。 //ExecutorType 为Executor的类型,TransactionIsolationLevel为事务隔离级别, autoCommit是否开启事务 //openSession的多个重载法可以指定获得的SeqSession的Executor类型和事务的处理 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try{ final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); //根据参数创建指定类型的Executor final Executor executor = configuration.newExecutor(tx, execType); //返回的是 DefaultSqlSession 执 sqlsession 中的 api 源码剖析-executor 继续源码中的步骤,进executor.query() return new DefaultSqlSession(configuration, executor, autoCommit); } catch(Exception e){ closeTransaction(tx); // may have fetched a connection so lets call close() } //8.进selectList法,多个重载法。 public List < E > selectList(String statement) { return this.selectList(statement, null); public List < E > selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT); public List < E > selectList(String statement, Object parameter, RowBounds rowBounds) { try { //根据传的全限定名+法名从映射的Map中取出MappedStatement对象 MappedStatement ms = configuration.getMappedStatement(statement); //调Executor中的法处理 //RowBounds是来逻辑分 // wrapCollection(parameter)是来装饰集合或者数组参数 return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException(“Error querying database. Cause: + e, e); } finally { ErrorContext.instance().reset(); } //此法在SimpleExecutor的类BaseExecutor中实现 public List 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); } //进query的重载法中 public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity(“executing a query”).object(ms.getId()); if (closed) { throw new ExecutorException(“Executor was closed.”); } if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List list; try { queryStack++; list = resultHandler == null ? (List) localCache.getObject(key) : null; if (list != null) { handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { //如果缓存中没有本次查找的值,那么从数据库中查询 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack–; } if (queryStack == 0) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list; } //从数据库查询 private List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List list; 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; } // SimpleExecutor中实现类的doQuery抽象法 public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); //传参数创建StatementHanlder对象来执查询 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); //创建jdbc中的statement对象 stmt = prepareStatement(handler, ms.getStatementLog()); // StatementHandler 进处理 return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } } //创建Statement的法 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; //条代码中的getConnection法经过重重调最后会调openConnection法,从连接池 中获 得连接。 Connection connection = getConnection(statementLog); stmt = handler.prepare(connection, transaction.getTimeout()); handler.parameterize(stmt); return stmt; } //从连接池获得连接的法 protected void openConnection() throws SQLException { if (log.isDebugEnabled()) { log.debug(“Opening JDBC Connection”); } //从连接池获得连接 上述的Executor.query()法经转折,最后会创建个StatementHandler对象,然后将必要的参数传 递给 StatementHandler,使StatementHandler来完成对数据库的查询,最终返回List结果集。 从上的代码中我们可以看出,Executor的功能和作是: 源码剖析-StatementHandler StatementHandler对象主要完成两个作: 对于JDBC的PreparedStatement类型的对象,创建的过程中,我们使的是SQL语句字符串会包 含若个?占位符,我们其后再对占位符进设值。StatementHandler通过 parameterize(statement)法对 S tatement 进设值; StatementHandler 通过 List query(Statement statement, ResultHandler resultHandler)法来 完成执Statement,和将Statement对象返回的resultSet封装成List; 进到 StatementHandler 的 parameterize(statement)法的实现: connection = dataSource.getConnection(); if (level != null) { connection.setTransactionIsolation(level.getLevel()); } } (1、根据传递的参数,完成SQL语句的动态解析,成BoundSql对象,供StatementHandler使; (2、为查询创建缓存,以提性能 (3、创建JDBC的Statement连接对象,传递给*StatementHandler*对象,返回List查询结果。 public void parameterize(Statement statement) throws SQLException { //使ParameterHandler对象来完成对Statement的设值 parameterHandler.setParameters((PreparedStatement) statement); } /** ParameterHandler 类的 setParameters(PreparedStatement ps) 实现 * 对某个Statement进设置参数 * */ public void setParameters(PreparedStatement ps) throws SQLException { ErrorContext.instance().activity(“setting parameters”).object(mappedStatement.getParameterMap().getId()); List parameterMappings = boundSql.getParameterMappings(); if (parameterMappings != null) { for (int i = 0; i < parameterMappings.size(); i++) { ParameterMapping parameterMapping = parameterMappings.get(i); if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; 从上述的代码可以看到,StatementHandler的parameterize(Statement)法调了 ParameterHandler的setParameters(statement)法, ParameterHandler的setParameters(Statement )法负责根据我们输的参数,对statement对象的 ?占位符处进赋值。 进到StatementHandler 的 List query(Statement statement, ResultHandler resultHandler)法的 实现: 从上述代码我们可以看出,StatementHandler 的List query(Statement statement, ResultHandler resultHandler)法的实现,是调了 ResultSetHandler 的 handleResultSets(Statement)法。 String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } // 每个 Mapping都有个 TypeHandler,根据 TypeHandler 来对 preparedStatement 进 设置参数 TypeHandler typeHandler = parameterMapping.getTypeHandler(); JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null) jdbcType = configuration.getJdbcTypeForNull(); //设置参数 typeHandler.setParameter(ps, i + 1, value, jdbcType); } } } } public List query(Statement statement, ResultHandler resultHandler) throws SQLException { // 1.调preparedStatemnt。execute()法,然后将resultSet交给ResultSetHandler处 理 PreparedStatement ps = (PreparedStatement) statement; ps.execute(); //2.使 ResultHandler 来处理 ResultSet return resultSetHandler. handleResultSets(ps); } ResultSetHandler 的 handleResultSets(Statement)法会将 Statement 语句执后成的 resultSet 结 果集转换成List结果集 public List handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity(“handling results”).object(mappedStatement.getId()); //多ResultSet的结果集合,每个ResultSet对应个Object对象。实际上,每 个 Object 是 List 对象。 //在不考虑存储过程的多ResultSet的情况,普通的查询,实际就个ResultSet,也 就是说, multipleResults最多就个元素。 final List multipleResults = new ArrayList<>(); int resultSetCount = 0; //获得个ResultSet对象,并封装成ResultSetWrapper对象 ResultSetWrapper rsw = getFirstResultSet(stmt); //获得ResultMap数组 //在不考虑存储过程的多ResultSet的情况,普通的查询,实际就个ResultSet,也 就是 说,resultMaps就个元素。 List resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); // 校验 while (rsw != null && resultMapCount > resultSetCount) { //获得ResultMap对象 ResultMap resultMap = resultMaps.get(resultSetCount); //处理ResultSet,将结果添加到multipleResults中 handleResultSet(rsw, resultMap, multipleResults, null); //获得下个ResultSet对象,并封装成ResultSetWrapper对象 rsw = getNextResultSet(stmt); //清理 cleanUpAfterHandlingResultSet(); // resultSetCount ++ resultSetCount++; } } //因为’mappedStatement.resultSets’只在存储过程中使,本系列暂时不考虑,忽略即可 String[] resultSets = mappedStatement.getResultSets(); if(resultSets!=null) { while (rsw != null && resultSetCount < resultSets.length) { ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]); if (parentMapping != null) { String nestedResultMapId = parentMapping.getNestedResultMapId(); ResultMap resultMap = configuration.getResultMap(nestedResultMapId); handleResultSet(rsw, resultMap, null, parentMapping); 10.2 Mapper代理式: 回顾下写法: 思考个问题,通常的Mapper接我们都没有实现的法却可以使,是为什么呢?答案很简单动态 代理 开始之前介绍下MyBatis初始化时对接的处理:MapperRegistry是Configuration中的个属性, 它内部维护个HashMap于存放mapper接的类,每个接对应个类。mappers中可以 配置接的包路径,或者某个具体的接类。 当解析mappers标签时,它会判断解析到的是mapper配置件时,会再将对应配置件中的增删 改 查标签 封装成MappedStatement对象,存mappedStatements中。(上介绍了)当 判断解析到接时,会 建此接对应的MapperProxyFactory对象,存HashMap中,key =接的字节码对象,value =此接 对应的MapperProxyFactory对象。 源码剖析-getmapper() } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } } //如果是multipleResults单元素,则取元素返回 return collapseSingleResultList(multipleResults); } public static void main(String[] args) { //前三步都相同 InputStream inputStream = Resources.getResourceAsStream(“sqlMapConfig.xml”); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = factory.openSession(); //这不再调SqlSession的api,是获得了接对象,调接中的法。 UserMapper mapper = sqlSession.getMapper(UserMapper.class); List list = mapper.getUserByName(“tom”); } 进 sqlSession.getMapper(UserMapper.class )中 //DefaultSqlSession 中的 getMapper public T getMapper(Class type) { return configuration.getMapper(type, this); } //configuration 中的给 g etMapper public T getMapper(Class type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } //MapperRegistry 中的 g etMapper public T getMapper(Class type, SqlSession sqlSession) { //从 MapperRegistry 中的 HashMap 中拿 MapperProxyFactory final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException(“Type ” + type + ” is not known to the MapperRegistry.”); } try { //通过动态代理成示例。 return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException(“Error getting mapper instance. Cause: ” + e, e); } } //MapperProxyFactory 类中的 newInstance 法 public T newInstance(SqlSession sqlSession) { //创建了 JDK动态代理的Handler类 final MapperProxy mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); //调了重载法 return newInstance(mapperProxy); } //MapperProxy 类,实现了 InvocationHandler 接 public class MapperProxy implements InvocationHandler, Serializable { //省略部分源码 private final SqlSession sqlSession; private final Class mapperInterface; private final Map<method, mappermethod=””> methodCache; //构造,传了 SqlSession,说明每个session中的代理对象的不同的! public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map<method, mappermethod=””> methodCache) { 源码剖析-invoke() 在动态代理返回了示例后,我们就可以直接调mapper类中的法了,但代理对象调法,执是 在MapperProxy中的invoke法中 进execute法: this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; } //省略部分源码 } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { //如果是Object定义的法,直接调 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } // 获得 MapperMethod 对象 final MapperMethod mapperMethod = cachedMapperMethod(method); //重点在这:MapperMethod最终调了执的法 return mapperMethod.execute(sqlSession, args); } public Object execute(SqlSession sqlSession, Object[] args) { Object result; //判断mapper中的法类型,最终调的还是SqlSession中的法 switch (command.getType()) { case INSERT: { //转换参数 Object param = method.convertArgsToSqlCommandParam(args); //执INSERT操作 // 转换 rowCount result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { //转换参数 Object param = method.convertArgsToSqlCommandParam(args); // 转换 rowCount result = rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { //转换参数 Object param = method.convertArgsToSqlCommandParam(args); // 转换 rowCount result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } case SELECT: //返回,并且有ResultHandler法参数,则将查询的结果,提交给 ResultHandler 进 处理 if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; //执查询,返回列表 } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); //执查询,返回Map } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); //执查询,返回Cursor } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); //执查询,返回单个对象 } else { //转换参数 Object param = method.convertArgsToSqlCommandParam(args); //查询单条 result = sqlSession.selectOne(command.getName(), param); if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException(“Unknown execution method for: ” + command.getName()); 10.3 级缓存源码剖析: 级缓存构建在级缓存之上,在收到查询请求时,MyBatis 先会查询级缓存,若级缓存未命 中,再去查询级缓存,级缓存没有,再查询数据库。 级缓存——》 级缓存——》数据库 与级缓存不同,级缓存和具体的命名空间绑定,个Mapper中有个Cache,相同Mapper中的 MappedStatement共个Cache,级缓存则是和 SqlSession 绑定。 启级缓存 分为三步: 1)开启全局级缓存配置: 2) 在需要使级缓存的Mapper配置件中配置标签 3)在具体CURD标签上配置 useCache=true 标签 < cache/> 的解析 根据之前的mybatis源码剖析,xml的解析作主要交给XMLConfigBuilder.parse()法来实现 } //返回结果为null,并且返回类型为基本类型,则抛出BindingException异常 if(result ==null&&method.getReturnType().isPrimitive() &&!method.returnsVoid()){ throw new BindingException(“Mapper method ‘” + command.getName() + ” attempted to return null from a method with a primitive return type(” + method.getReturnType() + “). “); } //返回结果 return result; }select * from user where id = #{id}// XMLConfigBuilder.parse() public Configuration parse() { if (parsed) { throw new BuilderException(“Each XMLConfigBuilder can only be used once.”); } parsed = true; parseConfiguration(parser.evalNode(“/configuration”));// 在这 return configuration; } // parseConfiguration() // 既然是在xml中添加的,那么我们就直接看关于mappers标签的解析 private void parseConfiguration(XNode root) { try { Properties settings = settingsAsPropertiess(root.evalNode(“settings”)); propertiesElement(root.evalNode(“properties”)); loadCustomVfs(settings); typeAliasesElement(root.evalNode(“typeAliases”)); pluginElement(root.evalNode(“plugins”)); objectFactoryElement(root.evalNode(“objectFactory”)); objectWrapperFactoryElement(root.evalNode(“objectWrapperFactory”)); reflectionFactoryElement(root.evalNode(“reflectionFactory”)); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 environmentsElement(root.evalNode(“environments”)); databaseIdProviderElement(root.evalNode(“databaseIdProvider”)); typeHandlerElement(root.evalNode(“typeHandlers”)); // 就是这 mapperElement(root.evalNode(“mappers”)); } catch (Exception e) { throw new BuilderException(“Error parsing SQL Mapper Configuration. Cause: ” + e, e); } } // mapperElement() private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if (“package”.equals(child.getName())) { String mapperPackage = child.getStringAttribute(“name”); configuration.addMappers(mapperPackage); } else { String resource = child.getStringAttribute(“resource”); String url = child.getStringAttribute(“url”); String mapperClass = child.getStringAttribute(“class”); 我们来看看解析Mapper.xml // 按照我们本例的配置,则直接该if判断 if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); // 成XMLMapperBuilder,并执其parse法 mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { Class mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { throw new BuilderException(“A mapper element may only specify a url, resource or class, but not more than one.”); } } } } } // XMLMapperBuilder.parse() public void parse() { if (!configuration.isResourceLoaded(resource)) { // 解析mapper属性 configurationElement(parser.evalNode(“/mapper”)); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingChacheRefs(); parsePendingStatements(); } // configurationElement() 先来看看是如何构建Cache对象的 MapperBuilderAssistant.useNewCache() private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute(“namespace”); if (namespace == null || namespace.equals(“”)) { throw new BuilderException(“Mapper’s namespace cannot be empty”); } builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode(“cache-ref”)); // 最终在这看到了关于cache属性的处理 cacheElement(context.evalNode(“cache”)); parameterMapElement(context.evalNodes(“/mapper/parameterMap”)); resultMapElements(context.evalNodes(“/mapper/resultMap”)); sqlElement(context.evalNodes(“/mapper/sql”)); // 这会将成的Cache包装到对应的MappedStatement buildStatementFromContext(context.evalNodes(“select|insert|update|delete”)); } catch (Exception e) { throw new BuilderException(“Error parsing Mapper XML. Cause: ” + e, e); } } // cacheElement() private void cacheElement(XNode context) throws Exception { if (context != null) { //解析标签的type属性,这我们可以定义cache的实现类,如redisCache, 如果没有定义,这使和级缓存相同的PERPETUAL String type = context.getStringAttribute(“type”, “PERPETUAL”); Class typeClass = typeAliasRegistry.resolveAlias(type); String eviction = context.getStringAttribute(“eviction”, “LRU”); Class evictionClass = typeAliasRegistry.resolveAlias(eviction); Long flushInterval = context.getLongAttribute(“flushInterval”); Integer size = context.getIntAttribute(“size”); boolean readWrite = !context.getBooleanAttribute(“readOnly”, false); boolean blocking = context.getBooleanAttribute(“blocking”, false); Properties props = context.getChildrenAsProperties(); // 构建Cache对象 builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props); } } public Cache useNewCache(Class typeClass, 我们看到个Mapper.xml只会解析次标签,也就是只创建次Cache对象,放进configuration中, 并将cache赋值给MapperBuilderAssistant.currentCache buildStatementFromContext(context.evalNodes(“select|insert|update|delete”));将Cache 包装到MappedStatement Class evictionClass, Long flushInterval, Integer size, boolean readWrite, boolean blocking, Properties props) { // 1.成Cache对象 Cache cache = new CacheBuilder(currentNamespace) //这如果我们定义了中的type,就使定义的Cache,否则使和级缓存相 同的PerpetualCache .implementation(valueOrDefault(typeClass, PerpetualCache.class)) .addDecorator(valueOrDefault(evictionClass, LruCache.class)) .clearInterval(flushInterval) .size(size) .readWrite(readWrite) .blocking(blocking) .properties(props) .build(); // 2.添加到Configuration中 configuration.addCache(cache); // 3.并将cache赋值给MapperBuilderAssistant.currentCache currentCache = cache; return cache; } // buildStatementFromContext() private void buildStatementFromContext(List list) { if (configuration.getDatabaseId() != null) { buildStatementFromContext(list, configuration.getDatabaseId()); } buildStatementFromContext(list, null); } //buildStatementFromContext() private void buildStatementFromContext(List list, String requiredDatabaseId) { for (XNode context : list) { final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { // 每条执语句转换成个MappedStatement statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } } } // XMLStatementBuilder.parseStatementNode(); public void parseStatementNode() { String id = context.getStringAttribute(“id”); String databaseId = context.getStringAttribute(“databaseId”); … Integer fetchSize = context.getIntAttribute(“fetchSize”); Integer timeout = context.getIntAttribute(“timeout”); String parameterMap = context.getStringAttribute(“parameterMap”); String parameterType = context.getStringAttribute(“parameterType”); Class parameterTypeClass = resolveClass(parameterType); String resultMap = context.getStringAttribute(“resultMap”); String resultType = context.getStringAttribute(“resultType”); String lang = context.getStringAttribute(“lang”); LanguageDriver langDriver = getLanguageDriver(lang); … // 创建MappedStatement对象 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); } // builderAssistant.addMappedStatement() public MappedStatement addMappedStatement( String id, …) { if (unresolvedCacheRef) { throw new IncompleteElementException(“Cache-ref not yet resolved”); } id = applyCurrentNamespace(id, false); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; //创建MappedStatement对象 MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType) … 我们看到将Mapper中创建的Cache对象,加到了每个MappedStatement对象中,也就是同个 Mapper中所有的2 有关于标签的解析就到这了。 查询源码分析 CachingExecutor .flushCacheRequired(valueOrDefault(flushCache, !isSelect)) .useCache(valueOrDefault(useCache, isSelect)) .cache(currentCache);// 在这将之前成的Cache封装到MappedStatement ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id); if (statementParameterMap != null) { statementBuilder.parameterMap(statementParameterMap); } MappedStatement statement = statementBuilder.build(); configuration.addMappedStatement(statement); return statement; } // CachingExecutor public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); // 创建 CacheKey CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { // 从 MappedStatement 中获取 Cache,注意这的 Cache 是从MappedStatement中获取的 // 也就是我们上解析Mapper中标签中创建的,它保存在Configration中 // 我们在上解析blog.xml时分析过每个MappedStatement都有个Cache对象,就是这 Cache cache = ms.getCache(); // 如果配置件中没有配置 ,则 cache 为空 if (cache != null) { //如果需要刷新缓存的话就刷新:flushCache=”true” flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, boundSql); // 访问级缓存 List list = (List) tcm.getObject(cache, key); // 缓存未命中 如果设置了flushCache=”true”,则每次查询都会刷新缓存 如上,注意级缓存是从 MappedStatement 中获取的。由于 MappedStatement 存在于全局配置 中,可以多个 CachingExecutor 获取到,这样就会出现线程安全问题。除此之外,若不加以控制,多个 事务共个缓存实例,会导致脏读问题。于脏读问题,需要借助其他类来处理,也就是上代码中 tcm 变量对应的类型。下分析下。 TransactionalCacheManager if (list == null) { // 如果没有值,则执查询,这个查询实际也是先级缓存查询,级缓存也没 有的话,则进DB查询 list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); // 缓存查询结果 tcm.putObject(cache, key, list); } return list; } } return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } select * from t_demo/** 事务缓存管理器 */ public class TransactionalCacheManager { // Cache 与 TransactionalCache 的映射关系表 private final Map<cache, transactionalcache=””> transactionalCaches = new HashMap<cache, transactionalcache=””>(); public void clear(Cache cache) { // 获取 TransactionalCache 对象,并调该对象的 clear 法,下同 getTransactionalCache(cache).clear(); } public Object getObject(Cache cache, CacheKey key) { // 直接从TransactionalCache中获取缓存 return getTransactionalCache(cache).getObject(key); } public void putObject(Cache cache, CacheKey key, Object value) { // 直接存TransactionalCache的缓存中 TransactionalCacheManager 内部维护了 Cache 实例与 TransactionalCache 实例间的映射关系,该类 也仅负责维护两者的映射关系,真正做事的还是 TransactionalCache。TransactionalCache 是种缓 存装饰器,可以为 Cache 实例增加事务功能。我在之前提到的脏读问题正是由该类进处理的。下分 析下该类的逻辑。 TransactionalCache getTransactionalCache(cache).putObject(key, value); } public void commit() { for (TransactionalCache txCache : transactionalCaches.values()) { txCache.commit(); } } public void rollback() { for (TransactionalCache txCache : transactionalCaches.values()) { txCache.rollback(); } } private TransactionalCache getTransactionalCache(Cache cache) { // 从映射表中获取 TransactionalCache TransactionalCache txCache = transactionalCaches.get(cache); if (txCache == null) { // TransactionalCache 也是种装饰类,为 Cache 增加事务功能 // 创建个新的TransactionalCache,并将真正的Cache对象存进去 txCache = new TransactionalCache(cache); transactionalCaches.put(cache, txCache); } return txCache; } } public class TransactionalCache implements Cache { //真正的缓存对象,和上的Map<cache, transactionalcache=””>中的Cache是同个 private final Cache delegate; private boolean clearOnCommit; // 在事务被提交前,所有从数据库中查询的结果将缓存在此集合中 private final Map<object, object=””> entriesToAddOnCommit; // 在事务被提交前,当缓存未命中时,CacheKey 将会被存储在此集合中 private final Set entriesMissedInCache; @Override public Object getObject(Object key) { // 查询的时候是直接从delegate中去查询的,也就是从真正的缓存对象中查询 Object object = delegate.getObject(key); if (object == null) { // 缓存未命中,则将 key 存到 entriesMissedInCache 中 entriesMissedInCache.add(key); } if (clearOnCommit) { return null; } else { return object; } } @Override public void putObject(Object key, Object object) { // 将键值对存到 entriesToAddOnCommit 这个Map中中,真实的缓存对象 delegate 中 entriesToAddOnCommit.put(key, object); } @Override public Object removeObject(Object key) { return null; } @Override public void clear() { clearOnCommit = true; // 清空 entriesToAddOnCommit,但不清空 delegate 缓存 entriesToAddOnCommit.clear(); } public void commit() { // 根据 clearOnCommit 的值决定是否清空 delegate if (clearOnCommit) { delegate.clear(); } // 刷新未缓存的结果到 delegate 缓存中 flushPendingEntries(); // 重置 entriesToAddOnCommit 和 entriesMissedInCache reset(); } public void rollback() { unlockMissedEntries(); reset(); } private void reset() { 存储级缓存对象的时候是放到了TransactionalCache.entriesToAddOnCommit这个map中,但是每 次查询的时候是直接从TransactionalCache.delegate中去查询的,所以这个级缓存查询数据库后,设 置缓存值是没有刻效的,主要是因为直接存到 delegate 会导致脏数据问题 为何只有SqlSession提交或关闭之后? 那我们来看下SqlSession.commit()法做了什么 SqlSession clearOnCommit = false; // 清空集合 entriesToAddOnCommit.clear(); entriesMissedInCache.clear(); } private void flushPendingEntries() { for (Map.Entry<object, object=””> entry : entriesToAddOnCommit.entrySet()) { // 将 entriesToAddOnCommit 中的内容转存到 delegate 中 delegate.putObject(entry.getKey(), entry.getValue()); } for (Object entry : entriesMissedInCache) { if (!entriesToAddOnCommit.containsKey(entry)) { // 存空值 delegate.putObject(entry, null); } } } private void unlockMissedEntries() { for (Object entry : entriesMissedInCache) { try { // 调 removeObject 进解锁 delegate.removeObject(entry); } catch (Exception e) { log.warn(“…”); } } } } @Override public void commit(boolean force) { try { // 主要是这句 executor.commit(isCommitOrRollbackRequired(force)); 级缓存的刷新 我们来看看SqlSession的更新操作 dirty = false; } catch (Exception e) { throw ExceptionFactory.wrapException(“Error committing transaction. Cause: ” + e, e); } finally { ErrorContext.instance().reset(); } } // CachingExecutor.commit() @Override public void commit(boolean required) throws SQLException { delegate.commit(required); tcm.commit();// 在这 } // TransactionalCacheManager.commit() public void commit() { for (TransactionalCache txCache : transactionalCaches.values()) { txCache.commit();// 在这 } } // TransactionalCache.commit() public void commit() { if (clearOnCommit) { delegate.clear(); } flushPendingEntries();//这句 reset(); } // TransactionalCache.flushPendingEntries() private void flushPendingEntries() { for (Map.Entry<object, object=””> entry : entriesToAddOnCommit.entrySet()) { // 在这真正的将entriesToAddOnCommit的对象逐个添加到delegate中,只有这时, 级缓存才真正的效 delegate.putObject(entry.getKey(), entry.getValue()); } for (Object entry : entriesMissedInCache) { if (!entriesToAddOnCommit.containsKey(entry)) { delegate.putObject(entry, null); } } } MyBatis级缓存只适于不常进增、删、改的数据,如国家政区省市区街道数据。但数据变 更,MyBatis会清空缓存。因此级缓存不适于经常进更新的数据。 总结: 在级缓存的设计上,MyBatis量地运了装饰者模式,如CachingExecutor, 以及各种Cache接的 装饰器。 级缓存实现了Sqlsession之间的缓存数据共享,属于namespace级别 级缓存具有丰富的缓存策略。 级缓存可由多个装饰器,与基础缓存组合成 级缓存作由 个缓存装饰执器CachingExecutor和 个事务型预缓存TransactionalCache 完成。 10.4 延迟加载源码剖析: public int update(String statement, Object parameter) { int var4; try { this.dirty = true; MappedStatement ms = this.configuration.getMappedStatement(statement); var4 = this.executor.update(ms, this.wrapCollection(parameter)); } catch (Exception var8) { throw ExceptionFactory.wrapException(“Error updating database. Cause: ” + var8, var8); } finally { ErrorContext.instance().reset(); } return var4; } public int update(MappedStatement ms, Object parameterObject) throws SQLException { this.flushCacheIfRequired(ms); return this.delegate.update(ms, parameterObject); } private void flushCacheIfRequired(MappedStatement ms) { //获取MappedStatement对应的Cache,进清空 Cache cache = ms.getCache(); //SQL需设置flushCache=”true” 才会执清空 if (cache != null && ms.isFlushCacheRequired()) { this.tcm.clear(cache); } } 什么是延迟加载? 问题 在开发过程中很多时候我们并不需要总是在加载户信息时就定要加载他的订单信息。此时就是我 们所说的延迟加载。 举个栗 延迟加载 就是在需要到数据时才进加载,不需要到数据时就不加载数据。延迟加载也称懒加载。 实现 局部延迟加载 在association和collection标签中都有个fetchType属性,通过修改它的值,可以修改局部的加载策 略。 * 在对多中,当我们有个户,它有个100个订单 在查询户的时候,要不要把关联的订单查出来? 在查询订单的时候,要不要把关联的户查出来? * 回答 在查询户时,户下的订单应该是,什么时候,什么时候查询。 在查询订单时,订单所属的户信息应该是随着订单起查询出来。 * 优点: 先从单表查询,需要时再从关联表去关联查询,提数据库性能,因为查询单表要关联查询多张表 速度要快。 * 缺点: 因为只有当需要到数据时,才会进数据库查询,这样在批量数据查询时,因为查询作也要消耗时 间,所以可能造成户等待时间变,造成户体验下降。 * 在多表中: 对多,多对多:通常情况下采延迟加载 对(多对):通常情况下采即加载 * 注意: 延迟加载是基于嵌套查询来实现的 全局延迟加载 在Mybatis的核配置件中可以使setting标签修改全局的加载策略。 注意 7.。 SELECT * FROM `user` SELECT * from orders延迟加载原理实现 它的原理是,使 CGLIB 或 Javassist( 默认 ) 创建标对象的代理对象。当调代理对象的延迟加载属 性的 getting 法时,进拦截器法。如调 a.getB().getName() 法,进拦截器的 invoke(…) 法,发现 a.getB() 需要延迟加载时,那么就会单独发送事先保存好的查询关联 B 对象的 SQL ,把 B 查询上来,然后调 a.setB(b) 法,于是 a 对象 b 属性就有值了,接着完 成 a.getB().getName() 法的调。这就是延迟加载的基本原理 总结:延迟加载主要是通过动态代理的形式实现,通过代理拦截到指定法,执数据加载。 延迟加载原理(源码剖析) MyBatis延迟加载主要使:Javassist,Cglib实现,类图展示: Setting 配置加载: public class Configuration { /** aggressiveLazyLoading: * 当开启时,任何法的调都会加载该对象的所有属性。否则,每个属性会按需加载(参考 lazyLoadTriggerMethods). * 默认为true * */ protected boolean aggressiveLazyLoading; /** * 延迟加载触发法 */ protected Set lazyLoadTriggerMethods = new HashSet (Arrays.asList(new String[] { “equals”, “clone”, “hashCode”, “toString” })); /** 是否开启延迟加载 */ protected boolean lazyLoadingEnabled = false; /** 延迟加载代理对象创建 Mybatis的查询结果是由ResultSetHandler接的handleResultSets()法处理的。ResultSetHandler 接只有个实现,DefaultResultSetHandler,接下来看下延迟加载相关的个核的法 * 默认使Javassist代理 * @param proxyFactory */ public void setProxyFactory(ProxyFactory proxyFactory) { if (proxyFactory == null) { proxyFactory = new JavassistProxyFactory(); } this.proxyFactory = proxyFactory; } //省略… }

//#mark 创建结果对象 private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { this.useConstructorMappings = false; // reset previous mapping result final List<Class<?>> constructorArgTypes = new ArrayList<Class<?>>(); final List<Object> constructorArgs = new ArrayList<Object>(); //#mark 创建返回的结果映射的真实对象 Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix); if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings(); for (ResultMapping propertyMapping : propertyMappings) { // 判断属性有没配置嵌套查询,如果有就创建代理对象 if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) { //#mark 创建延迟加载代理对象 resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); break; } } } this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result return resultObject; 默认采javassistProxy进代理对象的创建 JavasisstProxyFactory实现 } public class JavassistProxyFactory implements org.apache.ibatis.executor.loader.ProxyFactory { /** * 接实现 * @param target 标结果对象 * @param lazyLoader 延迟加载对象 * @param configuration 配置 * @param objectFactory 对象 * @param constructorArgTypes 构造参数类型 * @param constructorArgs 构造参数值 * @return */ @Override public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<? >> constructorArgTypes, List<Object> constructorArgs) { return EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); } //省略... /** * 代理对象实现,核逻辑执 */ private static class EnhancedResultObjectProxyImpl implements MethodHandler { /** * 创建代理对象 * @param type * @param callback * @param constructorArgTypes * @param constructorArgs * @return */ static Object crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { ProxyFactory enhancer = new ProxyFactory(); enhancer.setSuperclass(type); try { //通过获取对象法,判断是否存在该法 type.getDeclaredMethod(WRITE_REPLACE_METHOD); // ObjectOutputStream will call writeReplace of objects returned by writeReplace if (log.isDebugEnabled()) { log.debug(WRITE_REPLACE_METHOD + " method was found on bean " + type + ", make sure it returns this"); } } catch (NoSuchMethodException e) { //没找到该法,实现接 enhancer.setInterfaces(new Class[]{WriteReplaceInterface.class}); } catch (SecurityException e) { // nothing to do here } Object enhanced; Class<?>[] typesArray = constructorArgTypes.toArray(new Class[constructorArgTypes.size()]); Object[] valuesArray = constructorArgs.toArray(new Object[constructorArgs.size()]); try { //创建新的代理对象 enhanced = enhancer.create(typesArray, valuesArray); } catch (Exception e) { throw new ExecutorException("Error creating lazy proxy. Cause: " + e, e); } //设置代理执器 ((Proxy) enhanced).setHandler(callback); return enhanced; } /** * 代理对象执 * @param enhanced 原对象 * @param method 原对象法 * @param methodProxy 代理法 * @param args 法参数 * @return * @throws Throwable */ @Override public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable { final String methodName = method.getName(); try { synchronized (lazyLoader) { if (WRITE_REPLACE_METHOD.equals(methodName)) { //忽略暂未找到具体作 Object original; if (constructorArgTypes.isEmpty()) { original = objectFactory.create(type); } else { original = objectFactory.create(type, constructorArgTypes, constructorArgs); } PropertyCopier.copyBeanProperties(type, enhanced, original); if (lazyLoader.size() > 0) { return new JavassistSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs); } else { return original; } } else { //延迟加载数量于0 if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) { //aggressive 次加载性所有需要要延迟加载属性或者包含触发延迟加载法 if (aggressive || lazyLoadTriggerMethods.contains(methodName)) { log.debug("==> laze lod trigger method:" + methodName + ",proxy method:" + methodProxy.getName() + " class:" + enhanced.getClass()); //次全部加载 lazyLoader.loadAll(); } else if (PropertyNamer.isSetter(methodName)) { //判断是否为set法,set法不需要延迟加载 final String property = PropertyNamer.methodToProperty(methodName); lazyLoader.remove(property); } else if (PropertyNamer.isGetter(methodName)) { final String property = PropertyNamer.methodToProperty(methodName); if (lazyLoader.hasLoader(property)) { //延迟加载单个属性 lazyLoader.load(property); log.debug("load one :" + methodName); } } 注意事项 1. IDEA调试问题 当配置aggressiveLazyLoading=true,在使IDEA进调试的时候,如果断点打到 代理执逻辑当中,你会发现延迟加载的代码永远都不能进,总是会被提前执。 主要产的 原因在aggressiveLazyLoading,因为在调试的时候,IDEA的Debuger窗体中已经触发了延迟加载 对象的法。 第部分:设计模式 虽然我们都知道有3类23种设计模式,但是多停留在概念层,Mybatis源码中使了量的设计模 式,观察设计模式在其中的应,能够更深的理解设计模式 Mybati s少到了以下的设计模式的使: } } } return methodProxy.invoke(enhanced, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } } 模式 mybatis 体现 Builder 模式 例如SqlSessionFactoryBuilder、Environment;  法模式 例如SqlSessionFactory、TransactionFactory、LogFactory 单例模 式 例如 ErrorContext 和 LogFactory; 代理模 式 Mybatis实现的核,如MapperProxy、ConnectionLogger,的jdk的动态代理 还有executor.loader包使了 cglib或者javassist达到延迟加载的效果 组合模 式 例如SqlNode和各个类ChooseSqlNode等; 模板 法模式 例如 BaseExecutor 和 SimpleExecutor,还有 BaseTypeHandler 和所有的类例如 IntegerTypeHandler; 适配器 模式 例如Log的Mybatis接和它对jdbc、log4j等各种志框架的适配实现; 装饰者 模式 例如Cache包中的cache.decorators包中等各个装饰者的实现; 迭代器 模式 例如迭代器模式PropertyTokenizer; 接下来对Builder构建者模式、模式、代理模式进解读,先介绍模式身的知识,然后解读在 Mybatis中怎样应了该模式。 11.1 Builder构建者模式 Builder模式的定义是"将个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表 示。”,它属于创建类模式,般来说,如果个对象的构建较复杂,超出了构造函数所能包含的范 围,就可以使模式和Builder模式,相对于模式会产出个完整的产品,Builder应于更加 复杂的对象的构建,甚只会构建产品的个部分,直来说,就是使多个简单的对象步步构建 成个复杂的对象 例:使构建者设计模式来产computer 主要步骤: 1、将需要构建的标类分成多个部件(电脑可以分为主机、显示器、键盘、箱等部件); 2、 创建构建类; 3、 依次创建部件; 4、 将部件组装成标对象 1. 定义computer ComputerBuilder package com.lagou.dao; import org.apache.ibatis.binding.BindingException; import org.apache.ibatis.session.SqlSession; import java.util.Optional; public class Computer { private String displayer; private String mainUnit; private String mouse; private String keyboard; public String getDisplayer() { return displayer; } public void setDisplayer(String displayer) { this.displayer = displayer; } public String getMainUnit() { return mainUnit; } public void setMainUnit(String mainUnit) { this.mainUnit = mainUnit; } public String getMouse() { return mouse; } public void setMouse(String mouse) { this.mouse = mouse; } public String getKeyboard() { return keyboard; } public void setKeyboard(String keyboard) { this.keyboard = keyboard; } @Override public String toString() { return "Computer{" + "displayer='" + displayer + '\'' + ", mainUnit='" + mainUnit + '\'' + ", mouse='" + mouse + '\'' + ", keyboard='" + keyboard + '\'' + '}'; } public static class ComputerBuilder { 调 Mybatis中的体现 SqlSessionFactory 的构建过程: Mybatis的初始化作常复杂,不是只个构造函数就能搞定的。所以使了建造者模式,使了  量的Builder,进分层构造,核对象Configuration使了 XmlConfigBuilder来进构造 private ComputerBuilder target = new ComputerBuilder(); public Builder installDisplayer(String displayer) { target.setDisplayer(displayer); return this; } public Builder installMainUnit(String mainUnit) { target.setMainUnit(mainUnit); return this; } public Builder installMouse(String mouse) { target.setMouse(mouse); return this; } public Builder installKeybord(String keyboard) { target.setKeyboard(keyboard); return this; } public ComputerBuilder build() { return target; } } public static void main(String[]args){ ComputerBuilder computerBuilder=new ComputerBuilder(); computerBuilder.installDisplayer("显万器"); computerBuilder.installMainUnit("主机"); computerBuilder.installKeybord("键盘"); computerBuilder.installMouse("标"); Computer computer=computerBuilder.Builder(); System.out.println(computer); } 在Mybatis环境的初始化过程中,SqlSessionFactoryBuilder会调XMLConfigBuilder读取所有的 MybatisMapConfig.xml 和所有的 *Mapper.xml 件,构建 Mybatis 运的核对象 Configuration 对 象,然后将该Configuration对象作为参数构建个SqlSessionFactory对象。 private void parseConfiguration(XNode root) { try { //issue #117 read properties first //解析标签 propertiesElement(root.evalNode("properties")); // 解析 标签 Properties settings = settingsAsProperties(root.evalNode("settings")); //加载定义的VFS实现类 loadCustomVfs(settings); // 解析 标签 typeAliasesElement(root.evalNode("typeAliases")); //解析标签 pluginElement(root.evalNode("plugins")); // 解析 标签 objectFactoryElement(root.evaINode("obj ectFactory")); // 解析 标签 obj ectWrappe rFacto ryElement(root.evalNode("objectWrapperFactory")); // 解析 标签 reflectorFactoryElement(root.evalNode("reflectorFactory")); // 赋值 到 Configuration 属性 settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 // 解析 标签 environmentsElement(root.evalNode("environments")); // 解析 标签 其中 XMLConfigBuilder 在构建 Configuration 对象时,也会调 XMLMapperBuilder 于读取 *Mapper 件,XMLMapperBuilder会使XMLStatementBuilder来读取和build所有的SQL语句。 在这个过程中,有个相似的特点,就是这些Builder会读取件或者配置,然后做量的XpathParser 解析、配置或语法的解析、反射成对象、存结果缓存等步骤,这么多的作都不是个构造函数所 能包括的,因此量采了 Builder模式来解决 SqlSessionFactoryBuilder类根据不同的输参数来构建SqlSessionFactory这个对象 11.2 模式 在Mybatis中如SqlSessionFactory使的是模式,该没有那么复杂的逻辑,是个简单 模式。 简单模式(Simple Factory Pattern):称为静态法(Static Factory Method)模式,它属于创 建型模式。 在简单模式中,可以根据参数的不同返回不同类的实例。简单模式专定义个类来负责创建 其他类的实例,被创建的实例通常都具有共同的类 例:产电脑 假设有个电脑的代产商,它前已经可以代产联想电脑了,随着业务的拓展,这个代产 商还要产惠普的电脑,我们就需要个单独的类来专产电脑,这就到了简单模式。 下我们来实现简单模式: 1. 创建抽象产品类 我们创建个电脑的抽象产品类,他有个抽象法于启动电脑: databaseldProviderElement(root.evalNode("databaseldProvider")); } //解析标签 mapperElement(root.evalNode("mappers")); 2. 创建具体产品类 接着我们创建各个品牌的电脑,他们都继承了他们的类Computer,并实现了类的start法: 3. 创建类 接下来创建个类,它提供了个静态法createComputer来产电脑。你只需要传你 想 产的电脑的品牌,它就会实例化相应品牌的电脑对象 客户端调类 客户端调类,传“hp”产出惠普电脑并调该电脑对象的start法: public abstract class Computer { /** *产品的抽象法,由具体的产品类去实现 */ public abstract void start(); } public class LenovoComputer extends Computer{ @Override public void start() { System.out.println("联想电脑启动"); } public class HpComputer extends Computer{ @Override public void start() { System.out.println("惠普电脑启动"); } } import org.junit.runner.Computer; public class ComputerFactory { public static Computer createComputer(String type){ Computer mComputer=null; switch (type) { case "lenovo": mComputer=new LenovoComputer(); break; case "hp": mComputer=new HpComputer(); break; } return mComputer; } } Mybatis 体现: Mybatis中执Sql语句、获取Mappers、管理事务的核接SqlSession的创建过程使到了模 式。 有个 SqlSessionFactory 来负责 SqlSession 的创建 SqlSessionFactory 可以看到,该Factory的openSession ()法重载了很多个,分别 持autoCommit、Executor、Transaction等参数的输,来构建核的SqlSession对象。 在DefaultSqlSessionFactory的默认实现,有个法可以看出怎么产出个产品: public class CreatComputer { public static void main(String[]args){ ComputerFactory.createComputer("hp").start(); } } private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,boolean autoCommit){ Transaction tx=null; try{ final Environment environment=configuration.getEnvironment(); final TransactionFactory transactionFactory= getTransactionFactoryFromEnvironment(environment); tx=transactionFactory.newTransaction(environment.getDataSource(),level,autoCo mmit); 这是个openSession调的底层法,该法先从configuration读取对应的环境配置,然后初始化 TransactionFactory 获得个 Transaction 对象,然后通过 Transaction 获取个 Executor 对象,最 后通过configuration、Executor、是否autoCommit三个参数构建了 SqlSession 11.3 代理模式 代理模式(Proxy Pattern):给某个对象提供个代理,并由代理对象控制对原对象的引。代理模式 的英叫做Proxy,它是种对象结构型模式,代理模式分为静态代理和动态代理,我们来介绍动态代 理 举例: 创建个抽象类,Person接,使其拥有个没有返回值的doSomething法。 创建个名为Bob的Person接的实现类,使其实现doSomething法 (3) 创建JDK动态代理类,使其实现InvocationHandler接。拥有个名为target的变量,并创建 getTa rget获取代理对象法 //根据参数创建制定类型的Executor final Executor executor=configuration.newExecutor(tx,execType); //返回的是 DefaultSqlSession return new DefaultSqlSession(configuration,executor,autoCommit); }catch(Exception e){ closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: "+ e,e); }finally{ ErrorContext.instance().reset(); } } /** * 抽象类 */ public interface Person { void doSomething(); } /** * 创建个名为Bob的的实现类 */ public class Bob implements Person { public void doSomething() { System.out.println("Bob doing something!"); } } 创建JDK动态代理测试类J DKDynamicTest Mybatis中实现: /** * JDK动态代理 * 需实现 InvocationHandler 接 */ public class JDKDynamicProxy implements InvocationHandler { //被代理的对象 Person target; // JDKDynamicProxy 构造函数 public JDKDynamicProxy(Person person) { this.target = person; } //获取代理对象 public Person getTarget() { return (Person) Proxy.newProxylnstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } //动态代理invoke法 public Person invoke(Object proxy, Method method, Object[] args) throws Throwable { //被代理法前执 System.out.println("JDKDynamicProxy do something before!"); //执被代理的法 Person result = (Person) method.invoke(target, args); //被代理法后执 System.out.println("JDKDynamicProxy do something after!"); return result; } /** * JDK动态代理测试 */ public class JDKDynamicTest { public static void main(String[] args) { System.out.println("不使代理类,调doSomething法。"); //不使代理类 Person person = new Bob(); // 调 doSomething 法 person.doSomething(); System.out.println("分割线-----------"); System.out.println("使代理类,调doSomething法。"); //获取代理类 Person proxyPerson = new JDKDynamicProxy(new Bob()).getTarget(); // 调 doSomething 法 proxyPerson.doSomething(); } } 代理模式可以认为是Mybatis的核使的模式,正是由于这个模式,我们只需要编写Mapper.java接 ,不需要实现,由Mybati s后台帮我们完成具体SQL的执。 当我们使Configuration的getMapper法时,会调mapperRegistry.getMapper法,该法 会调 mapperProxyFactory.newInstance(sqlSession)来成个具体的代理: 在这,先通过T newInstance(SqlSession sqlSession)法会得到个MapperProxy对象,然后调T newInstance(MapperProxy mapperProxy)成代理对象然后返回。查看MapperProxy的代码,可 以看到如下内容: public class MapperProxyFactory { private final Class mapperInterface; private final Map<method, mappermethod=""> methodCache = new ConcurrentHashMap<method, mappermethod="">(); public MapperProxyFactory(Class mapperInterface) { this.mapperInterface = mapperInterface; } public Class getMapperInterface() { return mapperInterface; } public Map<method, mappermethod=""> getMethodCache() { return methodCache; @SuppressWarnings("unchecked") protected T newInstance(MapperProxy mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } } public class MapperProxy implements InvocationHandler, Serializable { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); 常典型的,该MapperProxy类实现了InvocationHandler接,并且实现了该接的invoke法。通 过这种式,我们只需要编写Mapper.java接类,当真正执个Mapper接的时候,就会转发给 MapperProxy.invoke法,该法则会调后续的 sqlSession.cud>executor.execute>prepareStatement 等系列法,完成 SQL 的执和返回 加餐:Mybatis-Plus 1. Mybatis-Plus概念 1.1 Mybatis-Plus介绍 官: https://mybatis.plus/ 或 https://mp.baomidou.com/ Mybatis-Plus介绍 MyBatis-Plus(简称 MP)是个 MyBatis 的增强具,在 MyBatis 的基础上只做增强不做改变,为简 化开发、提 效率。 } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); } 愿景 我们的愿景是成为 MyBatis 最好的搭档,就像 魂罗 中的 1P、2P,基友搭配,效率翻倍。 1.2 特性 侵:只做增强不做改变,引它不会对现有程产影响,如丝般顺滑 损耗:启动即会动注基本 CURD,性能基本损耗,直接向对象操作 强的 CRUD 操作:内置通 Mapper、通 Service,仅仅通过少量配置即可实现单表部分 CRUD 操作,更有强的条件构造器,满各类使需求 持 Lambda 形式调:通过 Lambda 表达式,便的编写各类查询条件,需再担字段写错 持主键动成:持多达 4 种主键策略(内含分布式唯 ID 成器 - Sequence),可由配 置,完美解决主键问题 持 ActiveRecord 模式:持 ActiveRecord 形式调,实体类只需继承 Model 类即可进强 的 CRUD 操作 持定义全局通操作:持全局通法注( Write once, use anywhere ) 内置代码成器:采代码或者 Maven 插件可快速成 Mapper 、 Model 、 Service 、 Controller 层代码,持模板引擎,更有超多定义配置等您来使 内置分插件:基于 MyBatis 物理分,开发者需关具体操作,配置好插件之后,写分等 同于普通 List 查询 分插件持多种数据库:持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、 Postgre、SQLServer 等多种数据库 内置性能分析插件:可输出 Sql 语句以及其执时间,建议开发测试时启该功能,能快速揪出慢 查询 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可定义拦截规则,预防 误操作 1.3 架构 1.4 作者 Mybatis-Plus是由baomidou(苞)组织开发并且开源的,前该组织概有30左右。 码云地址:https://gitee.com/organizations/baomidou 2. Mybatis-Plus快速 2.1 安装 全新的 MyBatis-Plus 3.0 版本基于 JDK8,提供了 lambda 形式的调,所以安装集成 MP3.0 要求 如下: JDK 8+ Maven or Gradle Release Spring Boot Maven: Spring MVC Maven: com.baomidou mybatis-plus-boot-starter 3.4.0 com.baomidou mybatis-plus 3.4.0 对于Mybatis整合MP有常常有三种法,分别是Mybatis+MP、Spring+Mybatis+MP、Spring Boot+Mybatis+MP。 2.2 创建数据库以及表 创建User表,其表结构如下: 2.3 创建程 -- 创建测试表 DROP TABLE IF EXISTS tb_user; CREATE TABLE user ( id BIGINT(20) NOT NULL COMMENT '主键ID', name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名', age INT(11) NULL DEFAULT NULL COMMENT '年龄', email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱', PRIMARY KEY (id) ); -- 插测试数据 INSERT INTO user (id, name, age, email) VALUES (1, 'Jone', 18, 'test1@baomidou.com'), (2, 'Jack', 20, 'test2@baomidou.com'), (3, 'Tom', 28, 'test3@baomidou.com'), (4, 'Sandy', 21, 'test4@baomidou.com'), (5, 'Billie', 24, 'test5@baomidou.com'); 导依赖:  com.baomidou mybatis-plus 3.1.1  mysql mysql-connector-java 5.1.47  com.alibaba druid 1.0.11  org.projectlombok lombok 1.18.4 2.4 Mybatis + MP 下演示,通过纯Mybatis与Mybatis-Plus整合。 创建Module junit junit 4.12 org.slf4j slf4j-log4j12 1.6.4 org.apache.maven.plugins maven-compiler-plugin 1.8 1.8  lagou-mybatis-plus com.lagou.mp 1.0-SNAPSHOT 4.0.0 log4j.properties: Mybatis实现查询User 第步,编写mybatis-config.xml件: lagou-mybatis-plus-simple log4j.rootLogger=DEBUG,A1 log4j.appender.A1=org.apache.log4j.ConsoleAppender log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=[%t] [%c]-[%p] %m%n      第步,编写User实体对象:(这使lombok进了进化bean操作) 第三步,编写UserMapper接: 第四步,编写UserMapper.xml件: 第五步,编写TestMybatis测试例: @Data // getter setter @toString @NoArgsConstructor @AllArgsConstructor public class User { private Long id; private String name; private Integer age; private String email; } public interface UserMapper { List findAll(); }  select * from userpublic class MPTest { @Test public void test1() throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); 测试结果: 注:如果实体类名称和表名称不致,可以在实体类上添加注解@TableName("指定数据库表名") Mybatis+MP实现查询User 第步,将UserMapper继承BaseMapper,将拥有了BaseMapper中的所有法: 第步,使MP中的MybatisSqlSessionFactoryBuilder进程构建: SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List all = mapper.findAll(); for (User user : all) { System.out.println(user); } } } User(id=1, name=Jone, age=18, email=test1@baomidou.com) User(id=2, name=Jack, age=20, email=test2@baomidou.com) User(id=3, name=Tom, age=28, email=test3@baomidou.com) User(id=4, name=Sandy, age=21, email=test4@baomidou.com) User(id=5, name=Billie, age=24, email=test5@baomidou.com) import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.lagou.pojo.User; public interface UserMapper extends BaseMapper { } @Test public void test2() throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); //这使的是MP中的MybatisSqlSessionFactoryBuilder SqlSessionFactory sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); // 可以调BaseMapper中定义的法 List all = mapper.selectList(null); for (User user : all) { 测试: 注:如果实体类名称和表名称不致,可以在实体类上添加注解@TableName("指定数据库表名") 简单说明: 由于使了 MybatisSqlSessionFactoryBuilder进了构建,继承的BaseMapper中的法就载 到了 SqlSession中,所以就可以直接使相关的法; 如图 2.5 Spring + Mybatis + MP 引了Spring框架,数据源、构建等作就交给了Spring管理。 创建Module System.out.println(user); } } User(id=1, name=Jone, age=18, email=test1@baomidou.com) User(id=2, name=Jack, age=20, email=test2@baomidou.com) User(id=3, name=Tom, age=28, email=test3@baomidou.com) User(id=4, name=Sandy, age=21, email=test4@baomidou.com) User(id=5, name=Billie, age=24, email=test5@baomidou.com)  lagou-mybatis-plus 实现查询User 第步,编写jdbc.properties 第步,编写applicationContext.xml com.lagou.mp 1.0-SNAPSHOT 4.0.0 lagou-mybatis-plus-spring 5.1.6.RELEASE org.springframework spring-webmvc ${spring.version} org.springframework spring-jdbc ${spring.version} org.springframework spring-test ${spring.version} jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/mp?serverTimezone=GMT%2B8&useSSL=false jdbc.username=root jdbc.password=root  第三步,编写User对象以及UserMapper接: 第四步,编写测试例:     @Data @NoArgsConstructor @AllArgsConstructor public class User { private Long id; private String name; private Integer age; private String email; } public interface UserMapper extends BaseMapper { List findAll(); } @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:applicationContext.xml") 2.6 SpringBoot + Mybatis + MP 使SpringBoot将进步的简化MP的整合,需要注意的是,由于使SpringBoot需要继承parent,所 以需要重新创 建程,并不是创建Module。 创建程 public class TestSpringMP { @Autowired private UserMapper userMapper; @Test public void test2() throws IOException { List users = this.userMapper.selectList(null); for (User user : users) { System.out.println(user); } } 导依赖 org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-logging org.springframework.boot spring-boot-starter-test test log4j.properties: 编写application.properties  org.projectlombok lombok true  com.baomidou mybatis-plus-boot-starter 3.1.1 mysqlmysql-connector-java5.1.47org.slf4jslf4j-log4j12org.springframework.bootspring-boot-maven-plugin log4j.rootLogger=DEBUG,A1 log4j.appender.A1=org.apache.log4j.ConsoleAppender log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=[%t] [%c]-[%p] %m%n 编写pojo 编写mapper 编写启动类 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mp? useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=tr ue&useSSL=false spring.datasource.username=root spring.datasource.password=root @Data @NoArgsConstructor @AllArgsConstructor public class User { private Long id; private String name; private Integer age; private String email; } } public interface UserMapper extends BaseMapper { } package com.lagou.mp; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; @MapperScan("com.lagou.mp.mapper") //设置mapper接的扫描包 @SpringBootApplication public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } 编写测试例 测试: 3. 通CRUD 通过前的学习,我们了解到通过继承BaseMapper就可以获取到各种各样的单表操作,接下来我们将 详细讲解这些操作。 } package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelect() { List userList = userMapper.selectList(null); for (User user : userList) { System.out.println(user); } } } User(id=1, name=Jone, age=18, email=test1@baomidou.com) User(id=2, name=Jack, age=20, email=test2@baomidou.com) User(id=3, name=Tom, age=28, email=test3@baomidou.com) User(id=4, name=Sandy, age=21, email=test4@baomidou.com) User(id=5, name=Billie, age=24, email=test5@baomidou.com) 3.1 插操作 法定义 测试例 /** * 插条记录 * * @param entity 实体对象. */ int insert(T entity); @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testInsert(){ User user = new User(); user.setAge(18); user.setEmail("test@lagou.cn"); user.setName("慕"); 测试 可以看到,数据已经写到了数据库,但是,id的值不正确,我们期望的是数据库增,实际是MP 成了id的值写到了数据库。 如何设置id的成策略呢? MP持的id策略: //返回的result是受影响的数,并不是增后的id int result = userMapper.insert(user); System.out.println(result); System.out.println(user.getId()); } } 1 1318744682116739074 package com.baomidou.mybatisplus.annotation; import lombok.Getter; /** * 成ID类型枚举类 * * @author hubin * @since 2015-11-10 */ @Getter public enum IdType { /** * 数据库ID增 */ AUTO(0), /** * 该类型为未设置主键类型 修改User对象: */ NONE(1), /** * 户输ID *
//#mark 创建结果对象 private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { this.useConstructorMappings = false; // reset previous mapping result final List<Class<?>> constructorArgTypes = new ArrayList<Class<?>>(); final List<Object> constructorArgs = new ArrayList<Object>(); //#mark 创建返回的结果映射的真实对象 Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix); if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings(); for (ResultMapping propertyMapping : propertyMappings) { // 判断属性有没配置嵌套查询,如果有就创建代理对象 if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) { //#mark 创建延迟加载代理对象 resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); break; } } } this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result return resultObject; 默认采javassistProxy进代理对象的创建 JavasisstProxyFactory实现 } public class JavassistProxyFactory implements org.apache.ibatis.executor.loader.ProxyFactory { /** * 接实现 * @param target 标结果对象 * @param lazyLoader 延迟加载对象 * @param configuration 配置 * @param objectFactory 对象 * @param constructorArgTypes 构造参数类型 * @param constructorArgs 构造参数值 * @return */ @Override public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<? >> constructorArgTypes, List<Object> constructorArgs) { return EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); } //省略... /** * 代理对象实现,核逻辑执 */ private static class EnhancedResultObjectProxyImpl implements MethodHandler { /** * 创建代理对象 * @param type * @param callback * @param constructorArgTypes * @param constructorArgs * @return */ static Object crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { ProxyFactory enhancer = new ProxyFactory(); enhancer.setSuperclass(type); try { //通过获取对象法,判断是否存在该法 type.getDeclaredMethod(WRITE_REPLACE_METHOD); // ObjectOutputStream will call writeReplace of objects returned by writeReplace if (log.isDebugEnabled()) { log.debug(WRITE_REPLACE_METHOD + " method was found on bean " + type + ", make sure it returns this"); } } catch (NoSuchMethodException e) { //没找到该法,实现接 enhancer.setInterfaces(new Class[]{WriteReplaceInterface.class}); } catch (SecurityException e) { // nothing to do here } Object enhanced; Class<?>[] typesArray = constructorArgTypes.toArray(new Class[constructorArgTypes.size()]); Object[] valuesArray = constructorArgs.toArray(new Object[constructorArgs.size()]); try { //创建新的代理对象 enhanced = enhancer.create(typesArray, valuesArray); } catch (Exception e) { throw new ExecutorException("Error creating lazy proxy. Cause: " + e, e); } //设置代理执器 ((Proxy) enhanced).setHandler(callback); return enhanced; } /** * 代理对象执 * @param enhanced 原对象 * @param method 原对象法 * @param methodProxy 代理法 * @param args 法参数 * @return * @throws Throwable */ @Override public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable { final String methodName = method.getName(); try { synchronized (lazyLoader) { if (WRITE_REPLACE_METHOD.equals(methodName)) { //忽略暂未找到具体作 Object original; if (constructorArgTypes.isEmpty()) { original = objectFactory.create(type); } else { original = objectFactory.create(type, constructorArgTypes, constructorArgs); } PropertyCopier.copyBeanProperties(type, enhanced, original); if (lazyLoader.size() > 0) { return new JavassistSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs); } else { return original; } } else { //延迟加载数量于0 if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) { //aggressive 次加载性所有需要要延迟加载属性或者包含触发延迟加载法 if (aggressive || lazyLoadTriggerMethods.contains(methodName)) { log.debug("==> laze lod trigger method:" + methodName + ",proxy method:" + methodProxy.getName() + " class:" + enhanced.getClass()); //次全部加载 lazyLoader.loadAll(); } else if (PropertyNamer.isSetter(methodName)) { //判断是否为set法,set法不需要延迟加载 final String property = PropertyNamer.methodToProperty(methodName); lazyLoader.remove(property); } else if (PropertyNamer.isGetter(methodName)) { final String property = PropertyNamer.methodToProperty(methodName); if (lazyLoader.hasLoader(property)) { //延迟加载单个属性 lazyLoader.load(property); log.debug("load one :" + methodName); } } 注意事项 1. IDEA调试问题 当配置aggressiveLazyLoading=true,在使IDEA进调试的时候,如果断点打到 代理执逻辑当中,你会发现延迟加载的代码永远都不能进,总是会被提前执。 主要产的 原因在aggressiveLazyLoading,因为在调试的时候,IDEA的Debuger窗体中已经触发了延迟加载 对象的法。 第部分:设计模式 虽然我们都知道有3类23种设计模式,但是多停留在概念层,Mybatis源码中使了量的设计模 式,观察设计模式在其中的应,能够更深的理解设计模式 Mybati s少到了以下的设计模式的使: } } } return methodProxy.invoke(enhanced, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } } 模式 mybatis 体现 Builder 模式 例如SqlSessionFactoryBuilder、Environment;  法模式 例如SqlSessionFactory、TransactionFactory、LogFactory 单例模 式 例如 ErrorContext 和 LogFactory; 代理模 式 Mybatis实现的核,如MapperProxy、ConnectionLogger,的jdk的动态代理 还有executor.loader包使了 cglib或者javassist达到延迟加载的效果 组合模 式 例如SqlNode和各个类ChooseSqlNode等; 模板 法模式 例如 BaseExecutor 和 SimpleExecutor,还有 BaseTypeHandler 和所有的类例如 IntegerTypeHandler; 适配器 模式 例如Log的Mybatis接和它对jdbc、log4j等各种志框架的适配实现; 装饰者 模式 例如Cache包中的cache.decorators包中等各个装饰者的实现; 迭代器 模式 例如迭代器模式PropertyTokenizer; 接下来对Builder构建者模式、模式、代理模式进解读,先介绍模式身的知识,然后解读在 Mybatis中怎样应了该模式。 11.1 Builder构建者模式 Builder模式的定义是"将个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表 示。”,它属于创建类模式,般来说,如果个对象的构建较复杂,超出了构造函数所能包含的范 围,就可以使模式和Builder模式,相对于模式会产出个完整的产品,Builder应于更加 复杂的对象的构建,甚只会构建产品的个部分,直来说,就是使多个简单的对象步步构建 成个复杂的对象 例:使构建者设计模式来产computer 主要步骤: 1、将需要构建的标类分成多个部件(电脑可以分为主机、显示器、键盘、箱等部件); 2、 创建构建类; 3、 依次创建部件; 4、 将部件组装成标对象 1. 定义computer ComputerBuilder package com.lagou.dao; import org.apache.ibatis.binding.BindingException; import org.apache.ibatis.session.SqlSession; import java.util.Optional; public class Computer { private String displayer; private String mainUnit; private String mouse; private String keyboard; public String getDisplayer() { return displayer; } public void setDisplayer(String displayer) { this.displayer = displayer; } public String getMainUnit() { return mainUnit; } public void setMainUnit(String mainUnit) { this.mainUnit = mainUnit; } public String getMouse() { return mouse; } public void setMouse(String mouse) { this.mouse = mouse; } public String getKeyboard() { return keyboard; } public void setKeyboard(String keyboard) { this.keyboard = keyboard; } @Override public String toString() { return "Computer{" + "displayer='" + displayer + '\'' + ", mainUnit='" + mainUnit + '\'' + ", mouse='" + mouse + '\'' + ", keyboard='" + keyboard + '\'' + '}'; } public static class ComputerBuilder { 调 Mybatis中的体现 SqlSessionFactory 的构建过程: Mybatis的初始化作常复杂,不是只个构造函数就能搞定的。所以使了建造者模式,使了  量的Builder,进分层构造,核对象Configuration使了 XmlConfigBuilder来进构造 private ComputerBuilder target = new ComputerBuilder(); public Builder installDisplayer(String displayer) { target.setDisplayer(displayer); return this; } public Builder installMainUnit(String mainUnit) { target.setMainUnit(mainUnit); return this; } public Builder installMouse(String mouse) { target.setMouse(mouse); return this; } public Builder installKeybord(String keyboard) { target.setKeyboard(keyboard); return this; } public ComputerBuilder build() { return target; } } public static void main(String[]args){ ComputerBuilder computerBuilder=new ComputerBuilder(); computerBuilder.installDisplayer("显万器"); computerBuilder.installMainUnit("主机"); computerBuilder.installKeybord("键盘"); computerBuilder.installMouse("标"); Computer computer=computerBuilder.Builder(); System.out.println(computer); } 在Mybatis环境的初始化过程中,SqlSessionFactoryBuilder会调XMLConfigBuilder读取所有的 MybatisMapConfig.xml 和所有的 *Mapper.xml 件,构建 Mybatis 运的核对象 Configuration 对 象,然后将该Configuration对象作为参数构建个SqlSessionFactory对象。 private void parseConfiguration(XNode root) { try { //issue #117 read properties first //解析标签 propertiesElement(root.evalNode("properties")); // 解析 标签 Properties settings = settingsAsProperties(root.evalNode("settings")); //加载定义的VFS实现类 loadCustomVfs(settings); // 解析 标签 typeAliasesElement(root.evalNode("typeAliases")); //解析标签 pluginElement(root.evalNode("plugins")); // 解析 标签 objectFactoryElement(root.evaINode("obj ectFactory")); // 解析 标签 obj ectWrappe rFacto ryElement(root.evalNode("objectWrapperFactory")); // 解析 标签 reflectorFactoryElement(root.evalNode("reflectorFactory")); // 赋值 到 Configuration 属性 settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 // 解析 标签 environmentsElement(root.evalNode("environments")); // 解析 标签 其中 XMLConfigBuilder 在构建 Configuration 对象时,也会调 XMLMapperBuilder 于读取 *Mapper 件,XMLMapperBuilder会使XMLStatementBuilder来读取和build所有的SQL语句。 在这个过程中,有个相似的特点,就是这些Builder会读取件或者配置,然后做量的XpathParser 解析、配置或语法的解析、反射成对象、存结果缓存等步骤,这么多的作都不是个构造函数所 能包括的,因此量采了 Builder模式来解决 SqlSessionFactoryBuilder类根据不同的输参数来构建SqlSessionFactory这个对象 11.2 模式 在Mybatis中如SqlSessionFactory使的是模式,该没有那么复杂的逻辑,是个简单 模式。 简单模式(Simple Factory Pattern):称为静态法(Static Factory Method)模式,它属于创 建型模式。 在简单模式中,可以根据参数的不同返回不同类的实例。简单模式专定义个类来负责创建 其他类的实例,被创建的实例通常都具有共同的类 例:产电脑 假设有个电脑的代产商,它前已经可以代产联想电脑了,随着业务的拓展,这个代产 商还要产惠普的电脑,我们就需要个单独的类来专产电脑,这就到了简单模式。 下我们来实现简单模式: 1. 创建抽象产品类 我们创建个电脑的抽象产品类,他有个抽象法于启动电脑: databaseldProviderElement(root.evalNode("databaseldProvider")); } //解析标签 mapperElement(root.evalNode("mappers")); 2. 创建具体产品类 接着我们创建各个品牌的电脑,他们都继承了他们的类Computer,并实现了类的start法: 3. 创建类 接下来创建个类,它提供了个静态法createComputer来产电脑。你只需要传你 想 产的电脑的品牌,它就会实例化相应品牌的电脑对象 客户端调类 客户端调类,传“hp”产出惠普电脑并调该电脑对象的start法: public abstract class Computer { /** *产品的抽象法,由具体的产品类去实现 */ public abstract void start(); } public class LenovoComputer extends Computer{ @Override public void start() { System.out.println("联想电脑启动"); } public class HpComputer extends Computer{ @Override public void start() { System.out.println("惠普电脑启动"); } } import org.junit.runner.Computer; public class ComputerFactory { public static Computer createComputer(String type){ Computer mComputer=null; switch (type) { case "lenovo": mComputer=new LenovoComputer(); break; case "hp": mComputer=new HpComputer(); break; } return mComputer; } } Mybatis 体现: Mybatis中执Sql语句、获取Mappers、管理事务的核接SqlSession的创建过程使到了模 式。 有个 SqlSessionFactory 来负责 SqlSession 的创建 SqlSessionFactory 可以看到,该Factory的openSession ()法重载了很多个,分别 持autoCommit、Executor、Transaction等参数的输,来构建核的SqlSession对象。 在DefaultSqlSessionFactory的默认实现,有个法可以看出怎么产出个产品: public class CreatComputer { public static void main(String[]args){ ComputerFactory.createComputer("hp").start(); } } private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,boolean autoCommit){ Transaction tx=null; try{ final Environment environment=configuration.getEnvironment(); final TransactionFactory transactionFactory= getTransactionFactoryFromEnvironment(environment); tx=transactionFactory.newTransaction(environment.getDataSource(),level,autoCo mmit); 这是个openSession调的底层法,该法先从configuration读取对应的环境配置,然后初始化 TransactionFactory 获得个 Transaction 对象,然后通过 Transaction 获取个 Executor 对象,最 后通过configuration、Executor、是否autoCommit三个参数构建了 SqlSession 11.3 代理模式 代理模式(Proxy Pattern):给某个对象提供个代理,并由代理对象控制对原对象的引。代理模式 的英叫做Proxy,它是种对象结构型模式,代理模式分为静态代理和动态代理,我们来介绍动态代 理 举例: 创建个抽象类,Person接,使其拥有个没有返回值的doSomething法。 创建个名为Bob的Person接的实现类,使其实现doSomething法 (3) 创建JDK动态代理类,使其实现InvocationHandler接。拥有个名为target的变量,并创建 getTa rget获取代理对象法 //根据参数创建制定类型的Executor final Executor executor=configuration.newExecutor(tx,execType); //返回的是 DefaultSqlSession return new DefaultSqlSession(configuration,executor,autoCommit); }catch(Exception e){ closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: "+ e,e); }finally{ ErrorContext.instance().reset(); } } /** * 抽象类 */ public interface Person { void doSomething(); } /** * 创建个名为Bob的的实现类 */ public class Bob implements Person { public void doSomething() { System.out.println("Bob doing something!"); } } 创建JDK动态代理测试类J DKDynamicTest Mybatis中实现: /** * JDK动态代理 * 需实现 InvocationHandler 接 */ public class JDKDynamicProxy implements InvocationHandler { //被代理的对象 Person target; // JDKDynamicProxy 构造函数 public JDKDynamicProxy(Person person) { this.target = person; } //获取代理对象 public Person getTarget() { return (Person) Proxy.newProxylnstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } //动态代理invoke法 public Person invoke(Object proxy, Method method, Object[] args) throws Throwable { //被代理法前执 System.out.println("JDKDynamicProxy do something before!"); //执被代理的法 Person result = (Person) method.invoke(target, args); //被代理法后执 System.out.println("JDKDynamicProxy do something after!"); return result; } /** * JDK动态代理测试 */ public class JDKDynamicTest { public static void main(String[] args) { System.out.println("不使代理类,调doSomething法。"); //不使代理类 Person person = new Bob(); // 调 doSomething 法 person.doSomething(); System.out.println("分割线-----------"); System.out.println("使代理类,调doSomething法。"); //获取代理类 Person proxyPerson = new JDKDynamicProxy(new Bob()).getTarget(); // 调 doSomething 法 proxyPerson.doSomething(); } } 代理模式可以认为是Mybatis的核使的模式,正是由于这个模式,我们只需要编写Mapper.java接 ,不需要实现,由Mybati s后台帮我们完成具体SQL的执。 当我们使Configuration的getMapper法时,会调mapperRegistry.getMapper法,该法 会调 mapperProxyFactory.newInstance(sqlSession)来成个具体的代理: 在这,先通过T newInstance(SqlSession sqlSession)法会得到个MapperProxy对象,然后调T newInstance(MapperProxy mapperProxy)成代理对象然后返回。查看MapperProxy的代码,可 以看到如下内容: public class MapperProxyFactory { private final Class mapperInterface; private final Map<method, mappermethod=""> methodCache = new ConcurrentHashMap<method, mappermethod="">(); public MapperProxyFactory(Class mapperInterface) { this.mapperInterface = mapperInterface; } public Class getMapperInterface() { return mapperInterface; } public Map<method, mappermethod=""> getMethodCache() { return methodCache; @SuppressWarnings("unchecked") protected T newInstance(MapperProxy mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } } public class MapperProxy implements InvocationHandler, Serializable { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); 常典型的,该MapperProxy类实现了InvocationHandler接,并且实现了该接的invoke法。通 过这种式,我们只需要编写Mapper.java接类,当真正执个Mapper接的时候,就会转发给 MapperProxy.invoke法,该法则会调后续的 sqlSession.cud>executor.execute>prepareStatement 等系列法,完成 SQL 的执和返回 加餐:Mybatis-Plus 1. Mybatis-Plus概念 1.1 Mybatis-Plus介绍 官: https://mybatis.plus/ 或 https://mp.baomidou.com/ Mybatis-Plus介绍 MyBatis-Plus(简称 MP)是个 MyBatis 的增强具,在 MyBatis 的基础上只做增强不做改变,为简 化开发、提 效率。 } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); } 愿景 我们的愿景是成为 MyBatis 最好的搭档,就像 魂罗 中的 1P、2P,基友搭配,效率翻倍。 1.2 特性 侵:只做增强不做改变,引它不会对现有程产影响,如丝般顺滑 损耗:启动即会动注基本 CURD,性能基本损耗,直接向对象操作 强的 CRUD 操作:内置通 Mapper、通 Service,仅仅通过少量配置即可实现单表部分 CRUD 操作,更有强的条件构造器,满各类使需求 持 Lambda 形式调:通过 Lambda 表达式,便的编写各类查询条件,需再担字段写错 持主键动成:持多达 4 种主键策略(内含分布式唯 ID 成器 - Sequence),可由配 置,完美解决主键问题 持 ActiveRecord 模式:持 ActiveRecord 形式调,实体类只需继承 Model 类即可进强 的 CRUD 操作 持定义全局通操作:持全局通法注( Write once, use anywhere ) 内置代码成器:采代码或者 Maven 插件可快速成 Mapper 、 Model 、 Service 、 Controller 层代码,持模板引擎,更有超多定义配置等您来使 内置分插件:基于 MyBatis 物理分,开发者需关具体操作,配置好插件之后,写分等 同于普通 List 查询 分插件持多种数据库:持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、 Postgre、SQLServer 等多种数据库 内置性能分析插件:可输出 Sql 语句以及其执时间,建议开发测试时启该功能,能快速揪出慢 查询 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可定义拦截规则,预防 误操作 1.3 架构 1.4 作者 Mybatis-Plus是由baomidou(苞)组织开发并且开源的,前该组织概有30左右。 码云地址:https://gitee.com/organizations/baomidou 2. Mybatis-Plus快速 2.1 安装 全新的 MyBatis-Plus 3.0 版本基于 JDK8,提供了 lambda 形式的调,所以安装集成 MP3.0 要求 如下: JDK 8+ Maven or Gradle Release Spring Boot Maven: Spring MVC Maven: com.baomidou mybatis-plus-boot-starter 3.4.0 com.baomidou mybatis-plus 3.4.0 对于Mybatis整合MP有常常有三种法,分别是Mybatis+MP、Spring+Mybatis+MP、Spring Boot+Mybatis+MP。 2.2 创建数据库以及表 创建User表,其表结构如下: 2.3 创建程 -- 创建测试表 DROP TABLE IF EXISTS tb_user; CREATE TABLE user ( id BIGINT(20) NOT NULL COMMENT '主键ID', name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名', age INT(11) NULL DEFAULT NULL COMMENT '年龄', email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱', PRIMARY KEY (id) ); -- 插测试数据 INSERT INTO user (id, name, age, email) VALUES (1, 'Jone', 18, 'test1@baomidou.com'), (2, 'Jack', 20, 'test2@baomidou.com'), (3, 'Tom', 28, 'test3@baomidou.com'), (4, 'Sandy', 21, 'test4@baomidou.com'), (5, 'Billie', 24, 'test5@baomidou.com'); 导依赖:  com.baomidou mybatis-plus 3.1.1  mysql mysql-connector-java 5.1.47  com.alibaba druid 1.0.11  org.projectlombok lombok 1.18.4 2.4 Mybatis + MP 下演示,通过纯Mybatis与Mybatis-Plus整合。 创建Module junit junit 4.12 org.slf4j slf4j-log4j12 1.6.4 org.apache.maven.plugins maven-compiler-plugin 1.8 1.8  lagou-mybatis-plus com.lagou.mp 1.0-SNAPSHOT 4.0.0 log4j.properties: Mybatis实现查询User 第步,编写mybatis-config.xml件: lagou-mybatis-plus-simple log4j.rootLogger=DEBUG,A1 log4j.appender.A1=org.apache.log4j.ConsoleAppender log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=[%t] [%c]-[%p] %m%n      第步,编写User实体对象:(这使lombok进了进化bean操作) 第三步,编写UserMapper接: 第四步,编写UserMapper.xml件: 第五步,编写TestMybatis测试例: @Data // getter setter @toString @NoArgsConstructor @AllArgsConstructor public class User { private Long id; private String name; private Integer age; private String email; } public interface UserMapper { List findAll(); }  select * from userpublic class MPTest { @Test public void test1() throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); 测试结果: 注:如果实体类名称和表名称不致,可以在实体类上添加注解@TableName("指定数据库表名") Mybatis+MP实现查询User 第步,将UserMapper继承BaseMapper,将拥有了BaseMapper中的所有法: 第步,使MP中的MybatisSqlSessionFactoryBuilder进程构建: SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List all = mapper.findAll(); for (User user : all) { System.out.println(user); } } } User(id=1, name=Jone, age=18, email=test1@baomidou.com) User(id=2, name=Jack, age=20, email=test2@baomidou.com) User(id=3, name=Tom, age=28, email=test3@baomidou.com) User(id=4, name=Sandy, age=21, email=test4@baomidou.com) User(id=5, name=Billie, age=24, email=test5@baomidou.com) import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.lagou.pojo.User; public interface UserMapper extends BaseMapper { } @Test public void test2() throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); //这使的是MP中的MybatisSqlSessionFactoryBuilder SqlSessionFactory sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); // 可以调BaseMapper中定义的法 List all = mapper.selectList(null); for (User user : all) { 测试: 注:如果实体类名称和表名称不致,可以在实体类上添加注解@TableName("指定数据库表名") 简单说明: 由于使了 MybatisSqlSessionFactoryBuilder进了构建,继承的BaseMapper中的法就载 到了 SqlSession中,所以就可以直接使相关的法; 如图 2.5 Spring + Mybatis + MP 引了Spring框架,数据源、构建等作就交给了Spring管理。 创建Module System.out.println(user); } } User(id=1, name=Jone, age=18, email=test1@baomidou.com) User(id=2, name=Jack, age=20, email=test2@baomidou.com) User(id=3, name=Tom, age=28, email=test3@baomidou.com) User(id=4, name=Sandy, age=21, email=test4@baomidou.com) User(id=5, name=Billie, age=24, email=test5@baomidou.com)  lagou-mybatis-plus 实现查询User 第步,编写jdbc.properties 第步,编写applicationContext.xml com.lagou.mp 1.0-SNAPSHOT 4.0.0 lagou-mybatis-plus-spring 5.1.6.RELEASE org.springframework spring-webmvc ${spring.version} org.springframework spring-jdbc ${spring.version} org.springframework spring-test ${spring.version} jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/mp?serverTimezone=GMT%2B8&useSSL=false jdbc.username=root jdbc.password=root  第三步,编写User对象以及UserMapper接: 第四步,编写测试例:     @Data @NoArgsConstructor @AllArgsConstructor public class User { private Long id; private String name; private Integer age; private String email; } public interface UserMapper extends BaseMapper { List findAll(); } @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:applicationContext.xml") 2.6 SpringBoot + Mybatis + MP 使SpringBoot将进步的简化MP的整合,需要注意的是,由于使SpringBoot需要继承parent,所 以需要重新创 建程,并不是创建Module。 创建程 public class TestSpringMP { @Autowired private UserMapper userMapper; @Test public void test2() throws IOException { List users = this.userMapper.selectList(null); for (User user : users) { System.out.println(user); } } 导依赖 org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-logging org.springframework.boot spring-boot-starter-test test log4j.properties: 编写application.properties  org.projectlombok lombok true  com.baomidou mybatis-plus-boot-starter 3.1.1 mysqlmysql-connector-java5.1.47org.slf4jslf4j-log4j12org.springframework.bootspring-boot-maven-plugin log4j.rootLogger=DEBUG,A1 log4j.appender.A1=org.apache.log4j.ConsoleAppender log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=[%t] [%c]-[%p] %m%n 编写pojo 编写mapper 编写启动类 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mp? useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=tr ue&useSSL=false spring.datasource.username=root spring.datasource.password=root @Data @NoArgsConstructor @AllArgsConstructor public class User { private Long id; private String name; private Integer age; private String email; } } public interface UserMapper extends BaseMapper { } package com.lagou.mp; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; @MapperScan("com.lagou.mp.mapper") //设置mapper接的扫描包 @SpringBootApplication public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } 编写测试例 测试: 3. 通CRUD 通过前的学习,我们了解到通过继承BaseMapper就可以获取到各种各样的单表操作,接下来我们将 详细讲解这些操作。 } package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelect() { List userList = userMapper.selectList(null); for (User user : userList) { System.out.println(user); } } } User(id=1, name=Jone, age=18, email=test1@baomidou.com) User(id=2, name=Jack, age=20, email=test2@baomidou.com) User(id=3, name=Tom, age=28, email=test3@baomidou.com) User(id=4, name=Sandy, age=21, email=test4@baomidou.com) User(id=5, name=Billie, age=24, email=test5@baomidou.com) 3.1 插操作 法定义 测试例 /** * 插条记录 * * @param entity 实体对象. */ int insert(T entity); @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testInsert(){ User user = new User(); user.setAge(18); user.setEmail("test@lagou.cn"); user.setName("慕"); 测试 可以看到,数据已经写到了数据库,但是,id的值不正确,我们期望的是数据库增,实际是MP 成了id的值写到了数据库。 如何设置id的成策略呢? MP持的id策略: //返回的result是受影响的数,并不是增后的id int result = userMapper.insert(user); System.out.println(result); System.out.println(user.getId()); } } 1 1318744682116739074 package com.baomidou.mybatisplus.annotation; import lombok.Getter; /** * 成ID类型枚举类 * * @author hubin * @since 2015-11-10 */ @Getter public enum IdType { /** * 数据库ID增 */ AUTO(0), /** * 该类型为未设置主键类型 修改User对象: */ NONE(1), /** * 户输ID *

This type can be filled by registering the dynamic fill plug-in

*/ INPUT(2), /* 以下3种类型、只有当插对象ID 为空,才动填充。 */ /** * 全局唯ID (idWorker) */ ID_WORKER(3), /** * 全局唯ID (UUID) */ UUID(4), /** * 字符串全局唯ID (idWorker 的字符串表示) */ ID_WORKER_STR(5); private final int key; IdType(int key) { this.key = key; } } package com.lagou.mp.pojo; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor @TableName("tb_user") public class User { @TableId(type = IdType.AUTO) //指定id类型为增 private Long id; 数据插成功: @TableField 在MP中通过@TableField注解可以指定字段的些属性,常常解决的问题有2个: 1、对象中的属性名和字段名不致的问题(驼峰) 2、对象中的属性字段在表中不存在的问题 使: 其他法,如字段不加查询字段: private String userName; private String password; private String name; private Integer age; private String email; } 效果: 3.2 更新操作 在MP中,更新操作有2种,种是根据id更新,另种是根据条件更新。 根据id更新 法定义: 测试: /** * 根据 ID 修改 * * @param entity 实体对象 */ int updateById(@Param(Constants.ENTITY) T entity); @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { 结果: 根据条件更新 法定义: 测试例: @Autowired private UserMapper userMapper; @Test public void testUpdateById() { User user = new User(); user.setId(6L); //主键 user.setAge(21); //更新的字段 //根据id更新,更新不为null的字段 this.userMapper.updateById(user); } } /** * 根据 whereEntity 条件,更新记录 * * @param entity 实体对象 (set 条件值,可以为 null) * @param updateWrapper 实体对象封装操作类(可以为 null,的 entity 于成 where 语句) */ int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper updateWrapper); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 或者,通过UpdateWrapper进更新: 测试结果: import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import net.minidev.json.writer.UpdaterMapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testUpdate() { User user = new User(); user.setAge(22); //更新的字段 //更新的条件 QueryWrapper wrapper = new QueryWrapper<>(); wrapper.eq("id", 6); //执更新操作 int result = this.userMapper.update(user, wrapper); System.out.println("result = " + result); } } @Test public void testUpdate() { //更新的条件以及字段 UpdateWrapper wrapper = new UpdateWrapper<>(); wrapper.eq("id", 6).set("age", 23); //执更新操作 int result = this.userMapper.update(null, wrapper); System.out.println("result = " + result); } 均可达到更新的效果。 关于wrapper更多的法后会详细讲解。 3.3 删除操作 deleteById 法定义: 测试例: [main] [com.lagou.mp.mapper.UserMapper.update]-[DEBUG] ==> Preparing: UPDATE tb_user SET age=? WHERE id = ? [main] [com.lagou.mp.mapper.UserMapper.update]-[DEBUG] ==> Parameters: 23(Integer), 6(Integer) [main] [com.lagou.mp.mapper.UserMapper.update]-[DEBUG] <== Updates: 1 /** * 根据 ID 删除 * * @param id 主键ID */ int deleteById(Serializable id); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testDeleteById() { //执删除操作 int result = this.userMapper.deleteById(6L); System.out.println("result = " + result); } 结果: 数据被删除。 deleteByMap 法定义: 测试例: } [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] ==> Preparing: DELETE FROM tb_user WHERE id=? [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] ==> Parameters: 6(Long) [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] <== Updates: 1 /** * 根据 columnMap 条件,删除记录 * * @param columnMap 表字段 map 对象 */ int deleteByMap(@Param(Constants.COLUMN_MAP) Map<string, object=""> columnMap); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.HashMap; import java.util.Map; @RunWith(SpringRunner.class) 结果: delete 法定义: 测试例: @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testDeleteByMap() { Map<string, object=""> columnMap = new HashMap<>(); columnMap.put("age",21); columnMap.put("name","慕"); //将columnMap中的元素设置为删除的条件,多个之间为and关系 int result = this.userMapper.deleteByMap(columnMap); System.out.println("result = " + result); } } [main] [com.lagou.mp.mapper.UserMapper.deleteByMap]-[DEBUG] ==> Preparing: DELETE FROM tb_user WHERE name = ? AND age = ? [main] [com.lagou.mp.mapper.UserMapper.deleteByMap]-[DEBUG] ==> Parameters:  慕(String), 21(Integer) [main] [com.lagou.mp.mapper.UserMapper.deleteByMap]-[DEBUG] <== Updates: 0 /** * 根据 entity 条件,删除记录 * * @param wrapper 实体对象封装操作类(可以为 null) */ int delete(@Param(Constants.WRAPPER) Wrapper wrapper); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; 结果: 3.3.4、deleteBatchIds 法定义: import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.HashMap; import java.util.Map; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testDeleteByMap() { User user = new User(); user.setAge(20); user.setName("慕"); //将实体对象进包装,包装为操作条件 QueryWrapper wrapper = new QueryWrapper<>(user); int result = this.userMapper.delete(wrapper); System.out.println("result = " + result); } } [main] [com.lagou.mp.mapper.UserMapper.delete]-[DEBUG] ==> Preparing: DELETE FROM tb_user WHERE name=? AND age=? [main] [com.lagou.mp.mapper.UserMapper.delete]-[DEBUG] ==> Parameters: 慕 (String), 20(Integer) [main] [com.lagou.mp.mapper.UserMapper.delete]-[DEBUG] <== Updates: 0 测试例: 结果: /** * 删除(根据ID 批量删除) * * @param idList 主键ID列表(不能为 null 以及 empty) */ int deleteBatchIds(@Param(Constants.COLLECTION) Collection idList); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.Arrays; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testDeleteByMap() { //根据id集合批量删除 int result = this.userMapper.deleteBatchIds(Arrays.asList(1L,10L,20L)); System.out.println("result = " + result); } } [main] [com.lagou.mp.mapper.UserMapper.deleteBatchIds]-[DEBUG] ==> Preparing: DELETE FROM tb_user WHERE id IN ( ? , ? , ? ) [main] [com.lagou.mp.mapper.UserMapper.deleteBatchIds]-[DEBUG] ==> Parameters: 1(Long), 10(Long), 20(Long) [main] [com.lagou.mp.mapper.UserMapper.deleteBatchIds]-[DEBUG] <== Updates: 1 3.4 查询操作 MP提供了多种查询操作,包括根据id查询、批量查询、查询单条数据、查询列表、分查询等操作。 3.4.1、selectById 法定义: 测试例: 结果: 3.4.2、selectBatchIds /** * 根据 ID 查询 * * @param id 主键ID */ T selectById(Serializable id); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelectById() { //根据id查询数据 User user = this.userMapper.selectById(2L); System.out.println("result = " + user); } } result = User(id=2, name=Jack, age=20, email=test2@baomidou.com) 法定义: 测试例: 结果: /** * 查询(根据ID 批量查询) * * @param idList 主键ID列表(不能为 null 以及 empty) */ List selectBatchIds(@Param(Constants.COLLECTION) Collection idList); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.Arrays; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelectBatchIds() { //根据id集合批量查询 List users = this.userMapper.selectBatchIds(Arrays.asList(2L, 3L, 10L)); for (User user : users) { System.out.println(user); } } } User(id=2, name=Jack, age=20, email=test2@baomidou.com) User(id=3, name=Tom, age=28, email=test3@baomidou.com) 3.4.3、selectOne 法定义: 测试例: 结果: 3.4.4、selectCount /** * 根据 entity 条件,查询条记录 * * @param queryWrapper 实体对象封装操作类(可以为 null) */ T selectOne(@Param(Constants.WRAPPER) Wrapper queryWrapper); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelectOne() { QueryWrapper wrapper = new QueryWrapper(); wrapper.eq("name", "jack"); //根据条件查询条数据,如果结果超过条会报错 User user = this.userMapper.selectOne(wrapper); System.out.println(user); } } User(id=2, name=Jack, age=20, email=test2@baomidou.com) 法定义: 测试例: 结果: 3.4.5、selectList 法定义: /** * 根据 Wrapper 条件,查询总记录数 * * @param queryWrapper 实体对象封装操作类(可以为 null) */ Integer selectCount(@Param(Constants.WRAPPER) Wrapper queryWrapper); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelectCount() { QueryWrapper wrapper = new QueryWrapper(); wrapper.gt("age", 23); //年龄于23岁 //根据条件查询数据条数 Integer count = this.userMapper.selectCount(wrapper); System.out.println("count = " + count); } } count = 2 测试例: 结果: /** * 根据 entity 条件,查询全部记录 * * @param queryWrapper 实体对象封装操作类(可以为 null) */ List selectList(@Param(Constants.WRAPPER) Wrapper queryWrapper); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelectList() { QueryWrapper wrapper = new QueryWrapper(); wrapper.gt("age", 23); //年龄于23岁 //根据条件查询数据 List users = this.userMapper.selectList(wrapper); for (User user : users) { System.out.println("user = " + user); } } } user = User(id=3, name=Tom, age=28, email=test3@baomidou.com) user = User(id=5, name=Billie, age=24, email=test5@baomidou.com) 3.4.6、selectPage 法定义: 配置分插件: 测试例: /** * 根据 entity 条件,查询全部记录(并翻) * * @param page 分查询条件(可以为 RowBounds.DEFAULT) * @param queryWrapper 实体对象封装操作类(可以为 null) */ IPage selectPage(IPage page, @Param(Constants.WRAPPER) Wrapper queryWrapper); package com.lagou.mp; 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(); } } package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; 结果: 3.5 SQL注的原理 前我们已经知道,MP在启动后会将BaseMapper中的系列的法注册到meppedStatements中, 那么究竟是如何注的呢?流程是怎么样的?下我们将起来分析下。 在MP中,ISqlInjector负责SQL的注作,它是个接,AbstractSqlInjector是它的实现类,实现 关系如下: import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelectPage() { QueryWrapper wrapper = new QueryWrapper(); wrapper.gt("age", 20); //年龄于20岁 Page page = new Page<>(1,1); //根据条件查询数据 IPage iPage = this.userMapper.selectPage(page, wrapper); System.out.println("数据总条数:" + iPage.getTotal()); System.out.println("总数:" + iPage.getPages()); List users = iPage.getRecords(); for (User user : users) { System.out.println("user = " + user); } } } 数据总条数:4 总数:4 user = User(id=3, name=Tom, age=28, email=test3@baomidou.com) 在AbstractSqlInjector中,主要是由inspectInject()法进注的,如下: 在实现法中, methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo)); 是关键,循环遍历法,进注。 最终调抽象法injectMappedStatement进真正的注: @Override public void inspectInject(MapperBuilderAssistant builderAssistant, Class mapperClass) { Class modelClass = extractModelClass(mapperClass); if (modelClass != null) { String className = mapperClass.toString(); Set mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration()); if (!mapperRegistryCache.contains(className)) { List methodList = this.getMethodList(); if (CollectionUtils.isNotEmpty(methodList)) { TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass); // 循环注定义法 methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo)); } else { logger.debug(mapperClass.toString() + ", No effective injection method was found."); } mapperRegistryCache.add(className); } } } 查看该法的实现: 以SelectById为例查看: /** * 注定义 MappedStatement * * @param mapperClass mapper 接 * @param modelClass mapper 泛型 * @param tableInfo 数据库表反射信息 * @return MappedStatement */ public abstract MappedStatement injectMappedStatement(Class mapperClass, Class modelClass, TableInfo tableInfo); 可以看到,成了SqlSource对象,再将SQL通过addSelectMappedStatement法添加到 meppedStatements中。 4. 配置 在MP中有量的配置,其中有部分是Mybatis原的配置,另部分是MP的配置,详情:https://m ybatis.plus/config/ 下我们对常的配置做讲解。 4.1、基本配置 4.1.1、configLocation MyBatis 配置件位置,如果有单独的 MyBatis 配置,请将其路径配置到 configLocation 中。 MyBatis Configuration 的具体内容请参考MyBatis 官档 Spring Boot: public class SelectById extends AbstractMethod { @Override public MappedStatement injectMappedStatement(Class mapperClass, Class modelClass, TableInfo tableInfo) { SqlMethod sqlMethod = SqlMethod.LOGIC_SELECT_BY_ID; SqlSource sqlSource = new RawSqlSource(configuration, String.format(sqlMethod.getSql(), sqlSelectColumns(tableInfo, false), tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getKeyProperty(), tableInfo.getLogicDeleteSql(true, false)), Object.class); return this.addSelectMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource, modelClass, tableInfo); } } mybatis-plus.config-location = classpath:mybatis-config.xml Spring MVC: 4.1.2、mapperLocations MyBatis Mapper 所对应的 XML 件位置,如果您在 Mapper 中有定义法(XML 中有定义实 现),需要进该配置,告诉 Mapper 所对应的 XML 件位置。 Spring Boot: Spring MVC: Maven 多模块项的扫描路径需以 classpath*: classpath*: 开头 (即加载多个 jar 包下的 XML 件) 测试: UserMapper.xml: mybatis-plus.mapper-locations = classpath*:mybatis/*.xml select * from tb_user where id = #{id}测试例: 运结果: 4.1.3、typeAliasesPackage package com.lagou.mp.mapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.mapper.BaseMapper; public interface UserMapper extends BaseMapper { User findById(Long id); } package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelectPage() { User user = this.userMapper.findById(2L); System.out.println(user); } } MyBaits 别名包扫描路径,通过该属性可以给包中的类注册别名,注册后在 Mapper 对应的 XML 件 中可以直接使类名,不使全限定的类名(即 XML 中调的时候不包含包名)。 Spring Boot: Spring MVC: 4.2、进阶配置 本部分(Configuration)的配置都为 MyBatis 原持的配置,这意味着您可以通过 MyBatis XML 配置件的形式进配置。 4.2.1、mapUnderscoreToCamelCase 类型: boolean 默认值: true 是否开启动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN(下划线命名) 到 经典 Java 属性名 aColumn(驼峰命名) 的类似映射。 注意: 此属性在 MyBatis 中原默认值为 false,在 MyBatis-Plus 中,此属性也将于成最终的 SQL 的 select body 如果您的数据库命名符合规则需使 @TableField 注解指定数据库字段名 示例(SpringBoot): 4.2.2、cacheEnabled 类型: boolean 默认值: true 全局地开启或关闭配置件中的所有映射器已经配置的任何缓存,默认为 true。 示例: mybatis-plus.type-aliases-package = com.lagou.mp.pojo #关闭动驼峰映射,该参数不能和mybatis-plus.config-location同时存在 mybatis-plus.configuration.map-underscore-to-camel-case=false 4.3、DB 策略配置 4.3.1、idType 类型: com.baomidou.mybatisplus.annotation.IdType 默认值: ID_WORKER 全局默认主键类型,设置后,即可省略实体对象中的@TableId(type = IdType.AUTO)配置。 示例: SpringBoot: SpringMVC: 4.3.2、tablePrefix 类型: String 默认值: null 表名前缀,全局配置后可省略@TableName()配置。 SpringBoot: SpringMVC: mybatis-plus.configuration.cache-enabled=false mybatis-plus.global-config.db-config.id-type=auto  mybatis-plus.global-config.db-config.table-prefix=tb_ 5. 条件构造器 在MP中,Wrapper接的实现类关系如下: 可以看到,AbstractWrapper和AbstractChainWrapper是重点实现,接下来我们重点学习 AbstractWrapper以及其类。 说明: QueryWrapper(LambdaQueryWrapper) 和 UpdateWrapper(LambdaUpdateWrapper) 的类 于成 sql 的 where 条件, entity 属性也于成 sql 的 where 条件 注意: entity 成的 where 条件与 使各个 api 成的 where 条件没有任何关联为 官档地址:https://mybatis.plus/guide/wrapper.html 5.1、allEq 5.1.1、说明 全部eq(或个别isNull) 个别参数说明: params : key 为数据库字段名, value 为字段值 null2IsNull : 为 true 则在 map 的 value 为 null 时调 isNull 法,为 false 时则忽 略 value 为 null 的 例1: allEq({id:1,name:"王",age:null}) ---> id = 1 and name = '王' and age is null 例2: allEq({id:1,name:"王",age:null}, false) ---> id = 1 and name = '王' 个别参数说明: filter : 过滤函数,是否允许字段传对条件中 params 与 null2IsNull : 同上 例1: allEq((k,v) -> k.indexOf("a") > 0, {id:1,name:"王",age:null}) --- > name = '王' and age is null 例2: allEq((k,v) -> k.indexOf("a") > 0, {id:1,name:"王",age:null}, false) ---> name = '王' 5.1.2、测试例 allEq(Map<r, v=""> params) allEq(Map<r, v=""> params, boolean null2IsNull) allEq(boolean condition, Map<r, v=""> params, boolean null2IsNull) allEq(BiPredicate<r, v=""> filter, Map<r, v=""> params) allEq(BiPredicate<r, v=""> filter, Map<r, v=""> params, boolean null2IsNull) allEq(boolean condition, BiPredicate<r, v=""> filter, Map<r, v=""> params, boolean null2IsNull) package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.HashMap; import java.util.List; import java.util.Map; 5.2、基本较操作 eq 等于 = ne 不等于 <> gt 于 > ge 于等于 >= lt 于 < le @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testWrapper() { QueryWrapper wrapper = new QueryWrapper<>(); //设置条件 Map<string,object> params = new HashMap<>(); params.put("name", "jack"); params.put("age", "20"); // wrapper.allEq(params);//SELECT * FROM tb_user WHERE password IS NULL AND name = ? AND age = ? // wrapper.allEq(params,false); //SELECT * FROM tb_user WHERE name = ? AND age = ? // wrapper.allEq((k, v) -> (k.equals("name") || k.equals("age")) ,params);//SELECT * FROM tb_user WHERE name = ? AND age = ? List users = this.userMapper.selectList(wrapper); for (User user : users) { System.out.println(user); } } } 于等于 <= between BETWEEN 值1 AND 值2 notBetween NOT BETWEEN 值1 AND 值2 in 字段 IN (value.get(0), value.get(1), ...) notIn 字段 NOT IN (v0, v1, ...) 测试例: package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; QueryWrapper wrapper = new QueryWrapper<>(); //SELECT id,name,age,email FROM tb_user WHERE password = ? AND age >= ? AND name IN (?,?,?) wrapper.eq("email", "test2@baomidou.com") .ge("age", 20) .in("name", "jack", "jone", "tom"); List users = this.userMapper.selectList(wrapper); for (User user : users) { System.out.println(user); } } } 5.3、模糊查询 like LIKE '%值%' 例: like("name", "王") ---> name like '%王%' notLike NOT LIKE '%值%' 例: notLike("name", "王") ---> name not like '%王%' likeLeft LIKE '%值' 例: likeLeft("name", "王") ---> name like '%王' likeRight LIKE '值%' 例: likeRight("name", "王") ---> name like '王%' 测试例: package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testWrapper() { QueryWrapper wrapper = new QueryWrapper<>(); //SELECT id,user_name,password,name,age,email FROM tb_user WHERE name LIKE ? //Parameters: %%(String) wrapper.like("name", ""); List users = this.userMapper.selectList(wrapper); 5.4、排序 orderBy 排序:ORDER BY 字段, ... 例: orderBy(true, true, "id", "name") ---> order by id ASC,name ASC orderByAsc 排序:ORDER BY 字段, ... ASC 例: orderByAsc("id", "name") ---> order by id ASC,name ASC orderByDesc 排序:ORDER BY 字段, ... DESC 例: orderByDesc("id", "name") ---> order by id DESC,name DESC 测试例: for (User user : users) { System.out.println(user); } } } package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testWrapper() { QueryWrapper wrapper = new QueryWrapper<>(); 5.5、逻辑查询 or 拼接 OR 主动调 or 表示紧接着下个法不是 and 连接!(不调 or 则默认为使 and 连接) and AND 嵌套 例: and(i -> i.eq("name", "李").ne("status", "活着")) ---> and (name = '李 ' and status <> '活着') 测试例: //SELECT id,user_name,password,name,age,email FROM tb_user ORDER BY age DESC wrapper.orderByDesc("age"); List users = this.userMapper.selectList(wrapper); for (User user : users) { System.out.println(user); } } } package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testWrapper() { QueryWrapper wrapper = new QueryWrapper<>(); 5.6、select 在MP查询中,默认查询所有的字段,如果有需要也可以通过select法进指定字段。 //SELECT id,user_name,password,name,age,email FROM tb_user WHERE name = ? OR age = ? wrapper.eq("name","jack").or().eq("age", 24); List users = this.userMapper.selectList(wrapper); for (User user : users) { System.out.println(user); } } } package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testWrapper() { QueryWrapper wrapper = new QueryWrapper<>(); //SELECT id,name,age FROM tb_user WHERE name = ? OR age = ? wrapper.eq("name", "jack") .or() .eq("age", 24) .select("id", "name", "age"); List users = this.userMapper.selectList(wrapper); 6. ActiveRecord ActiveRecord(简称AR)直受动态语( PHP 、 Ruby 等)的喜爱, Java 作为准静态语,对 于 ActiveRecord 往往只能感叹其优雅,所以我们也在 AR 道路上进了定的探索,希望家能够喜 欢。 什么是ActiveRecord? ActiveRecord也属于ORM(对象关系映射)层,由Rails最早提出,遵循标准的ORM模型:表映 射到记录,记录映射到对象,字段映射到对象属性。配合遵循的命名和配置惯例,能够很程度 的快速实现模型的操作,且简洁易懂。 ActiveRecord的主要思想是: 每个数据库表对应创建个类,类的每个对象实例对应于数据库中表的记录;通常 表的每个字段在类中都有相应的Field; ActiveRecord同时负责把持久化,在ActiveRecord中封装了对数据库的访问,即 CURD;; ActiveRecord是种领域模型(Domain Model),封装了部分业务逻辑; 6.1、开启AR之旅 在MP中,开启AR常简单,只需要将实体对象继承Model即可。 for (User user : users) { System.out.println(user); } } } package com.lagou.mp.pojo; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.extension.activerecord.Model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class User extends Model { 6.2、根据主键查询 6.3、新增数据 private Long id; private String userName; private String password; private String name; private Integer age; private String email; } @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testAR() { User user = new User(); user.setId(2L); User user2 = user.selectById(); System.out.println(user2); } } @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testARInsert() { User user = new User(); user.setName("应颠"); user.setAge(30); user.setEmail("yingdian@lagou.cn"); boolean insert = user.insert(); 结果: 6.4、更新操作 结果: System.out.println(insert); } } [main] [com.lagou.mp.mapper.UserMapper.insert]-[DEBUG] ==> Preparing: INSERT INTO tb_user ( name, age, email ) VALUES ( ?, ?, ?, ?, ? ) [main] [com.lagou.mp.mapper.UserMapper.insert]-[DEBUG] ==> Parameters: 应癫 (String), 30(Integer), liubei@lagou.cn(String) [main] [com.lagou.mp.mapper.UserMapper.insert]-[DEBUG] <== Updates: 1 @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testAR() { User user = new User(); user.setId(8L); user.setAge(35); boolean update = user.updateById(); System.out.println(update); } } 6.5、删除操作 结果: [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] ==> Preparing: UPDATE tb_user SET age=? WHERE id=? [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] ==> Parameters: 35(Integer), 8(Long) [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] <== Updates: 1 @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testAR() { User user = new User(); user.setId(7L); boolean delete = user.deleteById(); System.out.println(delete); } } [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] ==> Preparing: DELETE FROM tb_user WHERE id=? [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] ==> Parameters: 7(Long) [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] <== Updates: 1 6.6、根据条件查询 结果: 7. 插件 7.1、mybatis的插件机制 MyBatis 允许你在已映射语句执过程中的某点进拦截调。默认情况下,MyBatis 允许使插件 来拦截的法调包括: 1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) 2. ParameterHandler (getParameterObject, setParameters) 3. ResultSetHandler (handleResultSets, handleOutputParameters) 4. StatementHandler (prepare, parameterize, batch, update, query) 我们看到了可以拦截Executor接的部分法,如update,query,commit,rollback等法,还有 其他接的些法等。 总体概括为: 1. 拦截执器的法 @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testARFindById() { User user = new User(); QueryWrapper userQueryWrapper = new QueryWrapper<>(); userQueryWrapper.le("age","20"); List users = user.selectList(userQueryWrapper); for (User user1 : users) { System.out.println(user1); } } } User(id=1, name=Jone, age=18, email=test1@baomidou.com) User(id=2, name=Jack, age=20, email=test2@baomidou.com) User(id=7, name=慕, age=18, email=test@lagou.cn) 2. 拦截参数的处理 3. 拦截结果集的处理 4. 拦截Sql语法构建的处理 拦截器示例: 注到Spring容器: 或者通过xml配置,mybatis-config.xml: package com.lagou.mp.plugins; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; import java.util.Properties; @Intercepts({@Signature( type= Executor.class, method = "update", args = {MappedStatement.class,Object.class})}) public class MyInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { //拦截法,具体业务逻辑编写的位置 return invocation.proceed(); } @Override public Object plugin(Object target) { //创建target对象的代理对象,的是将当前拦截器加到该对象中 return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { //属性设置 } } /** * 定义拦截器 */ @Bean public MyInterceptor myInterceptor(){ return new MyInterceptor(); } 7.2、执分析插件 在MP中提供了对SQL执的分析的插件,可作阻断全表更新、删除的操作,注意:该插件仅适于开 发环境,不适于产环境。 SpringBoot配置: 测试: 结果:  @Bean public SqlExplainInterceptor sqlExplainInterceptor(){ SqlExplainInterceptor sqlExplainInterceptor = new SqlExplainInterceptor(); List sqlParserList = new ArrayList<>(); // 攻击 SQL 阻断解析器、加解析链 sqlParserList.add(new BlockAttackSqlParser()); sqlExplainInterceptor.setSqlParserList(sqlParserList); return sqlExplainInterceptor; } @Test public void testUpdate(){ User user = new User(); user.setAge(20); int result = this.userMapper.update(user, null); System.out.println("result = " + result); } 可以看到,当执全表更新时,会抛出异常,这样有效防了些误操作。 7.3、性能分析插件 性能分析拦截器,于输出每条 SQL 语句及其执时间,可以设置最执时间,超过时间会抛出异 常。 该插件只于开发环境,不建议产环境使。 配置: javaconfig式 Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of table update operation at com.baomidou.mybatisplus.core.toolkit.ExceptionUtils.mpe(ExceptionUtils.java:4 9) at com.baomidou.mybatisplus.core.toolkit.Assert.isTrue(Assert.java:38) at com.baomidou.mybatisplus.core.toolkit.Assert.notNull(Assert.java:72) at com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser.processUpdate( BlockAttackSqlParser.java:45) at com.baomidou.mybatisplus.core.parser.AbstractJsqlParser.processParser(Abstract JsqlParser.java:92) at com.baomidou.mybatisplus.core.parser.AbstractJsqlParser.parser(AbstractJsqlPar ser.java:67) at com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler.sqlParser (AbstractSqlParserHandler.java:76) at com.baomidou.mybatisplus.extension.plugins.SqlExplainInterceptor.intercept(Sql ExplainInterceptor.java:63) at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61) at com.sun.proxy.$Proxy70.update(Unknown Source) at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession. java:197) ... 41 more xml式 执结果: 可以看到,执时间为11ms。如果将maxTime设置为1,那么,该操作会抛出异常。 @Bean public PerformanceInterceptor performanceInterceptor(){ PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor(); performanceInterceptor.setMaxTime(100); performanceInterceptor.setFormat(true); return performanceInterceptor; }    Time:11 ms - ID:com.lagou.mp.mapper.UserMapper.selectById Execute SQL: SELECT id, user_name, password, name, age, email FROM tb_user WHERE id=7 7.4、乐观锁插件 7.4.1、主要适场景 意图: 当要更新条记录的时候,希望这条记录没有被别更新 乐观锁实现式: 取出记录时,获取当前version 更新时,带上这个version 执更新时, set version = newVersion where version = oldVersion 如果version不对,就更新失败 7.4.2、插件配置 spring xml: spring boot: 7.4.3、注解实体字段 需要为实体字段添加@Version注解。 第步,为表添加version字段,并且设置初始值为1: Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: The SQL execution time is too large, please optimize ! at com.baomidou.mybatisplus.core.toolkit.ExceptionUtils.mpe(ExceptionUtils.java:4 9) at com.baomidou.mybatisplus.core.toolkit.Assert.isTrue(Assert.java:38) ................ @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor() { return new OptimisticLockerInterceptor(); } 第步,为User实体对象添加version字段,并且添加@Version注解: 7.4.4、测试 测试例: 执志: ALTER TABLE `tb_user` ADD COLUMN `version` int(10) NULL AFTER `email`; UPDATE `tb_user` SET `version`='1'; @Version private Integer version; @Test public void testUpdate(){ User user = new User(); user.setAge(30); user.setId(2L); user.setVersion(1); //获取到version为1 int result = this.userMapper.updateById(user); System.out.println("result = " + result); } main] [com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser]- [DEBUG] Original SQL: UPDATE tb_user SET age=?, version=? WHERE id=? AND version=? [main] [com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser]- [DEBUG] parser sql: UPDATE tb_user SET age = ?, version = ? WHERE id = ? AND version = ? [main] [org.springframework.jdbc.datasource.DataSourceUtils]-[DEBUG] Fetching JDBC Connection from DataSource [main] [org.mybatis.spring.transaction.SpringManagedTransaction]-[DEBUG] JDBC Connection [HikariProxyConnection@540206885 wrapping com.mysql.jdbc.JDBC4Connection@27e0f2f5] will not be managed by Spring [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] ==> Preparing: UPDATE tb_user SET age=?, version=? WHERE id=? AND version=? [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] ==> Parameters: 30(Integer), 2(Integer), 2(Long), 1(Integer) [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] <== Updates: 1 [main] [org.mybatis.spring.SqlSessionUtils]-[DEBUG] Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@30135202] result = 1 可以看到,更新的条件中有version条件,并且更新的version为2。 如果再次执,更新则不成功。这样就避免了多同时更新时导致数据的不致。 7.4.5、特别说明 持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime 整数类型下 newVersion = oldVersion + 1 newVersion 会回写到 entity 中 仅持 updateById(id) 与 update(entity, wrapper) 法 在 update(entity, wrapper) 法下, wrapper 不能复!!! 8. Sql 注器 我们已经知道,在MP中,通过AbstractSqlInjector将BaseMapper中的法注到了Mybatis容器,这 样这些法才可以正常执。 那么,如果我们需要扩充BaseMapper中的法,该如何实现呢? 下我们以扩展findAll法为例进学习。 8.1、编写MyBaseMapper 其他的Mapper都可以继承该Mapper,这样实现了统的扩展。 如: 8.2、编写MySqlInjector package com.lagou.mp.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import java.util.List; public interface MyBaseMapper extends BaseMapper { List findAll(); } package com.lagou.mp.mapper; import com.lagou.mp.pojo.User; public interface UserMapper extends MyBaseMapper { User findById(Long id); } 如果直接继承AbstractSqlInjector的话,原有的BaseMapper中的法将失效,所以我们选择继承 DefaultSqlInjector进扩展。 8.3、编写FindAll package com.lagou.mp.sqlInjector; import com.baomidou.mybatisplus.core.injector.AbstractMethod; import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector; import java.util.List; public class MySqlInjector extends DefaultSqlInjector { @Override public List getMethodList() { List methodList = super.getMethodList(); methodList.add(new FindAll()); // 再扩充定义的法 list.add(new FindAll()); return methodList; } } package com.lagou.mp.sqlInjector; import com.baomidou.mybatisplus.core.enums.SqlMethod; import com.baomidou.mybatisplus.core.injector.AbstractMethod; import com.baomidou.mybatisplus.core.metadata.TableInfo; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlSource; public class FindAll extends AbstractMethod { @Override public MappedStatement injectMappedStatement(Class mapperClass, Class modelClass, TableInfo tableInfo) { String sqlMethod = "findAll"; String sql = "select * from " + tableInfo.getTableName(); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); return this.addSelectMappedStatement(mapperClass, sqlMethod, sqlSource, modelClass, tableInfo); } 8.4、注册到Spring容器 8.5、测试 输出的SQL: 此,我们实现了全局扩展SQL注器。 9. 动填充功能 有些时候我们可能会有这样的需求,插或者更新数据时,希望有些字段可以动填充数据,如密 码、version等。在MP中提供了这样的功能,可以实现动填充。 9.1、添加@TableField注解 为email添加动填充功能,在新增数据时有效。 FieldFill提供了多种模式选择: } /** * 定义SQL注器 */ @Bean public MySqlInjector mySqlInjector(){ return new MySqlInjector(); } @Test public void testFindAll(){ List users = this.userMapper.findAll(); for (User user : users) { System.out.println(user); } } [main] [com.lagou.mp.mapper.UserMapper.findAll]-[DEBUG] ==> Preparing: select * from tb_user [main] [com.lagou.mp.mapper.UserMapper.findAll]-[DEBUG] ==> Parameters: [main] [com.lagou.mp.mapper.UserMapper.findAll]-[DEBUG] <== Total: 10 @TableField(fill = FieldFill.INSERT) //插数据时进填充 private String version; public enum FieldFill { 9.2、编写MyMetaObjectHandler 9.3、测试 /** * 默认不处理 */ DEFAULT, /** * 插时填充字段 */ INSERT, /** * 更新时填充字段 */ UPDATE, /** * 插和更新时填充字段 */ INSERT_UPDATE } package com.lagou.mp.handler; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component; @Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { Object password = getFieldValByName("version", metaObject); if(null == password){ //字段为空,可以进填充 setFieldValByName("version", "123456", metaObject); } } @Override public void updateFill(MetaObject metaObject) { } } 结果: 10. 逻辑删除 开发系统时,有时候在实现功能时,删除操作需要实现逻辑删除,所谓逻辑删除就是将数据标记为删 除,并真正的物理删除(DELETE操作),查询时需要携带状态条件,确保被标记的数据不被查 询到。这样做的的就是避免数据被真正的删除。 MP就提供了这样的功能,便我们使,接下来我们起学习下。 10.1、修改表结构 为tb_user表增加deleted字段,于表示数据是否被删除,1代表删除,0代表未删除。 同时,也修改User实体,增加deleted属性并且添加@TableLogic注解: 10.2、配置 @Test public void testInsert(){ User user = new User(); user.setName("冰冰"); user.setAge(30); user.setVersion(1); int result = this.userMapper.insert(user); System.out.println("result = " + result); } ALTER TABLE `tb_user` ADD COLUMN `deleted` int(1) NULL DEFAULT 0 COMMENT '1代表删除,0代表未删除' AFTER `version`; @TableLogic private Integer deleted; application.properties: 10.3、测试 执的SQL: 测试查询: 执的SQL: # 逻辑已删除值(默认为 1) mybatis-plus.global-config.db-config.logic-delete-value=1 # 逻辑未删除值(默认为 0) mybatis-plus.global-config.db-config.logic-not-delete-value=0 @Test public void testDeleteById(){ this.userMapper.deleteById(2L); } [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] ==> Preparing: UPDATE tb_user SET deleted=1 WHERE id=? AND deleted=0 [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] ==> Parameters: 2(Long) [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] <== Updates: 1 @Test public void testSelectById(){ User user = this.userMapper.selectById(2L); System.out.println(user); } 可,已经实现了逻辑删除。 ### 11. 代码成器 AutoGenerator 是 MyBatis-Plus 的代码成器,通过 AutoGenerator 可以快速成 Entity、 Mapper、Mapper XML、Service、Controller 等各个模块的代码,极的提升了开发效率。 11.1、创建程 pom.xml: [main] [com.lagou.mp.mapper.UserMapper.selectById]-[DEBUG] ==> Preparing: SELECT id,user_name,password,name,age,email,version,deleted FROM tb_user WHERE id=? AND deleted=0 [main] [com.lagou.mp.mapper.UserMapper.selectById]-[DEBUG] ==> Parameters: 2(Long) [main] [com.lagou.mp.mapper.UserMapper.selectById]-[DEBUG] <== Total: 0  4.0.0 org.springframework.boot spring-boot-starter-parent 2.3.4.RELEASE  com.lagou lagou-mp-generator 0.0.1-SNAPSHOT lagou-mp-generator Demo project for Spring Boot 11 org.springframework.boot spring-boot-starter-test test  com.baomidou mybatis-plus-boot-starter 3.1.1 com.baomidou mybatis-plus-generator 3.1.1 org.springframework.boot spring-boot-starter-freemarker  mysql mysql-connector-java 5.1.47 org.slf4j slf4j-log4j12 org.springframework.boot spring-boot-starter-web org.projectlomboklomboktrueorg.springframework.bootspring-boot-maven-plugin 11.2、代码 package com.lagou.mp.generator; import java.util.ArrayList; import java.util.List; import java.util.Scanner; import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException; import com.baomidou.mybatisplus.core.toolkit.StringPool; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.generator.AutoGenerator; import com.baomidou.mybatisplus.generator.InjectionConfig; import com.baomidou.mybatisplus.generator.config.DataSourceConfig; import com.baomidou.mybatisplus.generator.config.FileOutConfig; import com.baomidou.mybatisplus.generator.config.GlobalConfig; import com.baomidou.mybatisplus.generator.config.PackageConfig; import com.baomidou.mybatisplus.generator.config.StrategyConfig; import com.baomidou.mybatisplus.generator.config.TemplateConfig; import com.baomidou.mybatisplus.generator.config.po.TableInfo; import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; /** *
*/ INPUT(2), /* 以下3种类型、只有当插对象ID 为空,才动填充。 */ /** * 全局唯ID (idWorker) */ ID_WORKER(3), /** * 全局唯ID (UUID) */ UUID(4), /** * 字符串全局唯ID (idWorker 的字符串表示) */ ID_WORKER_STR(5); private final int key; IdType(int key) { this.key = key; } } package com.lagou.mp.pojo; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor @TableName("tb_user") public class User { @TableId(type = IdType.AUTO) //指定id类型为增 private Long id; 数据插成功: @TableField 在MP中通过@TableField注解可以指定字段的些属性,常常解决的问题有2个: 1、对象中的属性名和字段名不致的问题(驼峰) 2、对象中的属性字段在表中不存在的问题 使: 其他法,如字段不加查询字段: private String userName; private String password; private String name; private Integer age; private String email; } 效果: 3.2 更新操作 在MP中,更新操作有2种,种是根据id更新,另种是根据条件更新。 根据id更新 法定义: 测试: /** * 根据 ID 修改 * * @param entity 实体对象 */ int updateById(@Param(Constants.ENTITY) T entity); @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { 结果: 根据条件更新 法定义: 测试例: @Autowired private UserMapper userMapper; @Test public void testUpdateById() { User user = new User(); user.setId(6L); //主键 user.setAge(21); //更新的字段 //根据id更新,更新不为null的字段 this.userMapper.updateById(user); } } /** * 根据 whereEntity 条件,更新记录 * * @param entity 实体对象 (set 条件值,可以为 null) * @param updateWrapper 实体对象封装操作类(可以为 null,的 entity 于成 where 语句) */ int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper updateWrapper); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 或者,通过UpdateWrapper进更新: 测试结果: import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import net.minidev.json.writer.UpdaterMapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testUpdate() { User user = new User(); user.setAge(22); //更新的字段 //更新的条件 QueryWrapper wrapper = new QueryWrapper<>(); wrapper.eq("id", 6); //执更新操作 int result = this.userMapper.update(user, wrapper); System.out.println("result = " + result); } } @Test public void testUpdate() { //更新的条件以及字段 UpdateWrapper wrapper = new UpdateWrapper<>(); wrapper.eq("id", 6).set("age", 23); //执更新操作 int result = this.userMapper.update(null, wrapper); System.out.println("result = " + result); } 均可达到更新的效果。 关于wrapper更多的法后会详细讲解。 3.3 删除操作 deleteById 法定义: 测试例: [main] [com.lagou.mp.mapper.UserMapper.update]-[DEBUG] ==> Preparing: UPDATE tb_user SET age=? WHERE id = ? [main] [com.lagou.mp.mapper.UserMapper.update]-[DEBUG] ==> Parameters: 23(Integer), 6(Integer) [main] [com.lagou.mp.mapper.UserMapper.update]-[DEBUG] <== Updates: 1 /** * 根据 ID 删除 * * @param id 主键ID */ int deleteById(Serializable id); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testDeleteById() { //执删除操作 int result = this.userMapper.deleteById(6L); System.out.println("result = " + result); } 结果: 数据被删除。 deleteByMap 法定义: 测试例: } [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] ==> Preparing: DELETE FROM tb_user WHERE id=? [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] ==> Parameters: 6(Long) [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] <== Updates: 1 /** * 根据 columnMap 条件,删除记录 * * @param columnMap 表字段 map 对象 */ int deleteByMap(@Param(Constants.COLUMN_MAP) Map<string, object=""> columnMap); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.HashMap; import java.util.Map; @RunWith(SpringRunner.class) 结果: delete 法定义: 测试例: @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testDeleteByMap() { Map<string, object=""> columnMap = new HashMap<>(); columnMap.put("age",21); columnMap.put("name","慕"); //将columnMap中的元素设置为删除的条件,多个之间为and关系 int result = this.userMapper.deleteByMap(columnMap); System.out.println("result = " + result); } } [main] [com.lagou.mp.mapper.UserMapper.deleteByMap]-[DEBUG] ==> Preparing: DELETE FROM tb_user WHERE name = ? AND age = ? [main] [com.lagou.mp.mapper.UserMapper.deleteByMap]-[DEBUG] ==> Parameters:  慕(String), 21(Integer) [main] [com.lagou.mp.mapper.UserMapper.deleteByMap]-[DEBUG] <== Updates: 0 /** * 根据 entity 条件,删除记录 * * @param wrapper 实体对象封装操作类(可以为 null) */ int delete(@Param(Constants.WRAPPER) Wrapper wrapper); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; 结果: 3.3.4、deleteBatchIds 法定义: import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.HashMap; import java.util.Map; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testDeleteByMap() { User user = new User(); user.setAge(20); user.setName("慕"); //将实体对象进包装,包装为操作条件 QueryWrapper wrapper = new QueryWrapper<>(user); int result = this.userMapper.delete(wrapper); System.out.println("result = " + result); } } [main] [com.lagou.mp.mapper.UserMapper.delete]-[DEBUG] ==> Preparing: DELETE FROM tb_user WHERE name=? AND age=? [main] [com.lagou.mp.mapper.UserMapper.delete]-[DEBUG] ==> Parameters: 慕 (String), 20(Integer) [main] [com.lagou.mp.mapper.UserMapper.delete]-[DEBUG] <== Updates: 0 测试例: 结果: /** * 删除(根据ID 批量删除) * * @param idList 主键ID列表(不能为 null 以及 empty) */ int deleteBatchIds(@Param(Constants.COLLECTION) Collection idList); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.Arrays; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testDeleteByMap() { //根据id集合批量删除 int result = this.userMapper.deleteBatchIds(Arrays.asList(1L,10L,20L)); System.out.println("result = " + result); } } [main] [com.lagou.mp.mapper.UserMapper.deleteBatchIds]-[DEBUG] ==> Preparing: DELETE FROM tb_user WHERE id IN ( ? , ? , ? ) [main] [com.lagou.mp.mapper.UserMapper.deleteBatchIds]-[DEBUG] ==> Parameters: 1(Long), 10(Long), 20(Long) [main] [com.lagou.mp.mapper.UserMapper.deleteBatchIds]-[DEBUG] <== Updates: 1 3.4 查询操作 MP提供了多种查询操作,包括根据id查询、批量查询、查询单条数据、查询列表、分查询等操作。 3.4.1、selectById 法定义: 测试例: 结果: 3.4.2、selectBatchIds /** * 根据 ID 查询 * * @param id 主键ID */ T selectById(Serializable id); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelectById() { //根据id查询数据 User user = this.userMapper.selectById(2L); System.out.println("result = " + user); } } result = User(id=2, name=Jack, age=20, email=test2@baomidou.com) 法定义: 测试例: 结果: /** * 查询(根据ID 批量查询) * * @param idList 主键ID列表(不能为 null 以及 empty) */ List selectBatchIds(@Param(Constants.COLLECTION) Collection idList); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.Arrays; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelectBatchIds() { //根据id集合批量查询 List users = this.userMapper.selectBatchIds(Arrays.asList(2L, 3L, 10L)); for (User user : users) { System.out.println(user); } } } User(id=2, name=Jack, age=20, email=test2@baomidou.com) User(id=3, name=Tom, age=28, email=test3@baomidou.com) 3.4.3、selectOne 法定义: 测试例: 结果: 3.4.4、selectCount /** * 根据 entity 条件,查询条记录 * * @param queryWrapper 实体对象封装操作类(可以为 null) */ T selectOne(@Param(Constants.WRAPPER) Wrapper queryWrapper); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelectOne() { QueryWrapper wrapper = new QueryWrapper(); wrapper.eq("name", "jack"); //根据条件查询条数据,如果结果超过条会报错 User user = this.userMapper.selectOne(wrapper); System.out.println(user); } } User(id=2, name=Jack, age=20, email=test2@baomidou.com) 法定义: 测试例: 结果: 3.4.5、selectList 法定义: /** * 根据 Wrapper 条件,查询总记录数 * * @param queryWrapper 实体对象封装操作类(可以为 null) */ Integer selectCount(@Param(Constants.WRAPPER) Wrapper queryWrapper); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelectCount() { QueryWrapper wrapper = new QueryWrapper(); wrapper.gt("age", 23); //年龄于23岁 //根据条件查询数据条数 Integer count = this.userMapper.selectCount(wrapper); System.out.println("count = " + count); } } count = 2 测试例: 结果: /** * 根据 entity 条件,查询全部记录 * * @param queryWrapper 实体对象封装操作类(可以为 null) */ List selectList(@Param(Constants.WRAPPER) Wrapper queryWrapper); package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelectList() { QueryWrapper wrapper = new QueryWrapper(); wrapper.gt("age", 23); //年龄于23岁 //根据条件查询数据 List users = this.userMapper.selectList(wrapper); for (User user : users) { System.out.println("user = " + user); } } } user = User(id=3, name=Tom, age=28, email=test3@baomidou.com) user = User(id=5, name=Billie, age=24, email=test5@baomidou.com) 3.4.6、selectPage 法定义: 配置分插件: 测试例: /** * 根据 entity 条件,查询全部记录(并翻) * * @param page 分查询条件(可以为 RowBounds.DEFAULT) * @param queryWrapper 实体对象封装操作类(可以为 null) */ IPage selectPage(IPage page, @Param(Constants.WRAPPER) Wrapper queryWrapper); package com.lagou.mp; 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(); } } package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; 结果: 3.5 SQL注的原理 前我们已经知道,MP在启动后会将BaseMapper中的系列的法注册到meppedStatements中, 那么究竟是如何注的呢?流程是怎么样的?下我们将起来分析下。 在MP中,ISqlInjector负责SQL的注作,它是个接,AbstractSqlInjector是它的实现类,实现 关系如下: import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelectPage() { QueryWrapper wrapper = new QueryWrapper(); wrapper.gt("age", 20); //年龄于20岁 Page page = new Page<>(1,1); //根据条件查询数据 IPage iPage = this.userMapper.selectPage(page, wrapper); System.out.println("数据总条数:" + iPage.getTotal()); System.out.println("总数:" + iPage.getPages()); List users = iPage.getRecords(); for (User user : users) { System.out.println("user = " + user); } } } 数据总条数:4 总数:4 user = User(id=3, name=Tom, age=28, email=test3@baomidou.com) 在AbstractSqlInjector中,主要是由inspectInject()法进注的,如下: 在实现法中, methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo)); 是关键,循环遍历法,进注。 最终调抽象法injectMappedStatement进真正的注: @Override public void inspectInject(MapperBuilderAssistant builderAssistant, Class mapperClass) { Class modelClass = extractModelClass(mapperClass); if (modelClass != null) { String className = mapperClass.toString(); Set mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration()); if (!mapperRegistryCache.contains(className)) { List methodList = this.getMethodList(); if (CollectionUtils.isNotEmpty(methodList)) { TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass); // 循环注定义法 methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo)); } else { logger.debug(mapperClass.toString() + ", No effective injection method was found."); } mapperRegistryCache.add(className); } } } 查看该法的实现: 以SelectById为例查看: /** * 注定义 MappedStatement * * @param mapperClass mapper 接 * @param modelClass mapper 泛型 * @param tableInfo 数据库表反射信息 * @return MappedStatement */ public abstract MappedStatement injectMappedStatement(Class mapperClass, Class modelClass, TableInfo tableInfo); 可以看到,成了SqlSource对象,再将SQL通过addSelectMappedStatement法添加到 meppedStatements中。 4. 配置 在MP中有量的配置,其中有部分是Mybatis原的配置,另部分是MP的配置,详情:https://m ybatis.plus/config/ 下我们对常的配置做讲解。 4.1、基本配置 4.1.1、configLocation MyBatis 配置件位置,如果有单独的 MyBatis 配置,请将其路径配置到 configLocation 中。 MyBatis Configuration 的具体内容请参考MyBatis 官档 Spring Boot: public class SelectById extends AbstractMethod { @Override public MappedStatement injectMappedStatement(Class mapperClass, Class modelClass, TableInfo tableInfo) { SqlMethod sqlMethod = SqlMethod.LOGIC_SELECT_BY_ID; SqlSource sqlSource = new RawSqlSource(configuration, String.format(sqlMethod.getSql(), sqlSelectColumns(tableInfo, false), tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getKeyProperty(), tableInfo.getLogicDeleteSql(true, false)), Object.class); return this.addSelectMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource, modelClass, tableInfo); } } mybatis-plus.config-location = classpath:mybatis-config.xml Spring MVC: 4.1.2、mapperLocations MyBatis Mapper 所对应的 XML 件位置,如果您在 Mapper 中有定义法(XML 中有定义实 现),需要进该配置,告诉 Mapper 所对应的 XML 件位置。 Spring Boot: Spring MVC: Maven 多模块项的扫描路径需以 classpath*: classpath*: 开头 (即加载多个 jar 包下的 XML 件) 测试: UserMapper.xml: mybatis-plus.mapper-locations = classpath*:mybatis/*.xml select * from tb_user where id = #{id}测试例: 运结果: 4.1.3、typeAliasesPackage package com.lagou.mp.mapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.mapper.BaseMapper; public interface UserMapper extends BaseMapper { User findById(Long id); } package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testSelectPage() { User user = this.userMapper.findById(2L); System.out.println(user); } } MyBaits 别名包扫描路径,通过该属性可以给包中的类注册别名,注册后在 Mapper 对应的 XML 件 中可以直接使类名,不使全限定的类名(即 XML 中调的时候不包含包名)。 Spring Boot: Spring MVC: 4.2、进阶配置 本部分(Configuration)的配置都为 MyBatis 原持的配置,这意味着您可以通过 MyBatis XML 配置件的形式进配置。 4.2.1、mapUnderscoreToCamelCase 类型: boolean 默认值: true 是否开启动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN(下划线命名) 到 经典 Java 属性名 aColumn(驼峰命名) 的类似映射。 注意: 此属性在 MyBatis 中原默认值为 false,在 MyBatis-Plus 中,此属性也将于成最终的 SQL 的 select body 如果您的数据库命名符合规则需使 @TableField 注解指定数据库字段名 示例(SpringBoot): 4.2.2、cacheEnabled 类型: boolean 默认值: true 全局地开启或关闭配置件中的所有映射器已经配置的任何缓存,默认为 true。 示例: mybatis-plus.type-aliases-package = com.lagou.mp.pojo #关闭动驼峰映射,该参数不能和mybatis-plus.config-location同时存在 mybatis-plus.configuration.map-underscore-to-camel-case=false 4.3、DB 策略配置 4.3.1、idType 类型: com.baomidou.mybatisplus.annotation.IdType 默认值: ID_WORKER 全局默认主键类型,设置后,即可省略实体对象中的@TableId(type = IdType.AUTO)配置。 示例: SpringBoot: SpringMVC: 4.3.2、tablePrefix 类型: String 默认值: null 表名前缀,全局配置后可省略@TableName()配置。 SpringBoot: SpringMVC: mybatis-plus.configuration.cache-enabled=false mybatis-plus.global-config.db-config.id-type=auto  mybatis-plus.global-config.db-config.table-prefix=tb_ 5. 条件构造器 在MP中,Wrapper接的实现类关系如下: 可以看到,AbstractWrapper和AbstractChainWrapper是重点实现,接下来我们重点学习 AbstractWrapper以及其类。 说明: QueryWrapper(LambdaQueryWrapper) 和 UpdateWrapper(LambdaUpdateWrapper) 的类 于成 sql 的 where 条件, entity 属性也于成 sql 的 where 条件 注意: entity 成的 where 条件与 使各个 api 成的 where 条件没有任何关联为 官档地址:https://mybatis.plus/guide/wrapper.html 5.1、allEq 5.1.1、说明 全部eq(或个别isNull) 个别参数说明: params : key 为数据库字段名, value 为字段值 null2IsNull : 为 true 则在 map 的 value 为 null 时调 isNull 法,为 false 时则忽 略 value 为 null 的 例1: allEq({id:1,name:"王",age:null}) ---> id = 1 and name = '王' and age is null 例2: allEq({id:1,name:"王",age:null}, false) ---> id = 1 and name = '王' 个别参数说明: filter : 过滤函数,是否允许字段传对条件中 params 与 null2IsNull : 同上 例1: allEq((k,v) -> k.indexOf("a") > 0, {id:1,name:"王",age:null}) --- > name = '王' and age is null 例2: allEq((k,v) -> k.indexOf("a") > 0, {id:1,name:"王",age:null}, false) ---> name = '王' 5.1.2、测试例 allEq(Map<r, v=""> params) allEq(Map<r, v=""> params, boolean null2IsNull) allEq(boolean condition, Map<r, v=""> params, boolean null2IsNull) allEq(BiPredicate<r, v=""> filter, Map<r, v=""> params) allEq(BiPredicate<r, v=""> filter, Map<r, v=""> params, boolean null2IsNull) allEq(boolean condition, BiPredicate<r, v=""> filter, Map<r, v=""> params, boolean null2IsNull) package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.HashMap; import java.util.List; import java.util.Map; 5.2、基本较操作 eq 等于 = ne 不等于 <> gt 于 > ge 于等于 >= lt 于 < le @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testWrapper() { QueryWrapper wrapper = new QueryWrapper<>(); //设置条件 Map<string,object> params = new HashMap<>(); params.put("name", "jack"); params.put("age", "20"); // wrapper.allEq(params);//SELECT * FROM tb_user WHERE password IS NULL AND name = ? AND age = ? // wrapper.allEq(params,false); //SELECT * FROM tb_user WHERE name = ? AND age = ? // wrapper.allEq((k, v) -> (k.equals("name") || k.equals("age")) ,params);//SELECT * FROM tb_user WHERE name = ? AND age = ? List users = this.userMapper.selectList(wrapper); for (User user : users) { System.out.println(user); } } } 于等于 <= between BETWEEN 值1 AND 值2 notBetween NOT BETWEEN 值1 AND 值2 in 字段 IN (value.get(0), value.get(1), ...) notIn 字段 NOT IN (v0, v1, ...) 测试例: package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; QueryWrapper wrapper = new QueryWrapper<>(); //SELECT id,name,age,email FROM tb_user WHERE password = ? AND age >= ? AND name IN (?,?,?) wrapper.eq("email", "test2@baomidou.com") .ge("age", 20) .in("name", "jack", "jone", "tom"); List users = this.userMapper.selectList(wrapper); for (User user : users) { System.out.println(user); } } } 5.3、模糊查询 like LIKE '%值%' 例: like("name", "王") ---> name like '%王%' notLike NOT LIKE '%值%' 例: notLike("name", "王") ---> name not like '%王%' likeLeft LIKE '%值' 例: likeLeft("name", "王") ---> name like '%王' likeRight LIKE '值%' 例: likeRight("name", "王") ---> name like '王%' 测试例: package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testWrapper() { QueryWrapper wrapper = new QueryWrapper<>(); //SELECT id,user_name,password,name,age,email FROM tb_user WHERE name LIKE ? //Parameters: %%(String) wrapper.like("name", ""); List users = this.userMapper.selectList(wrapper); 5.4、排序 orderBy 排序:ORDER BY 字段, ... 例: orderBy(true, true, "id", "name") ---> order by id ASC,name ASC orderByAsc 排序:ORDER BY 字段, ... ASC 例: orderByAsc("id", "name") ---> order by id ASC,name ASC orderByDesc 排序:ORDER BY 字段, ... DESC 例: orderByDesc("id", "name") ---> order by id DESC,name DESC 测试例: for (User user : users) { System.out.println(user); } } } package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testWrapper() { QueryWrapper wrapper = new QueryWrapper<>(); 5.5、逻辑查询 or 拼接 OR 主动调 or 表示紧接着下个法不是 and 连接!(不调 or 则默认为使 and 连接) and AND 嵌套 例: and(i -> i.eq("name", "李").ne("status", "活着")) ---> and (name = '李 ' and status <> '活着') 测试例: //SELECT id,user_name,password,name,age,email FROM tb_user ORDER BY age DESC wrapper.orderByDesc("age"); List users = this.userMapper.selectList(wrapper); for (User user : users) { System.out.println(user); } } } package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testWrapper() { QueryWrapper wrapper = new QueryWrapper<>(); 5.6、select 在MP查询中,默认查询所有的字段,如果有需要也可以通过select法进指定字段。 //SELECT id,user_name,password,name,age,email FROM tb_user WHERE name = ? OR age = ? wrapper.eq("name","jack").or().eq("age", 24); List users = this.userMapper.selectList(wrapper); for (User user : users) { System.out.println(user); } } } package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testWrapper() { QueryWrapper wrapper = new QueryWrapper<>(); //SELECT id,name,age FROM tb_user WHERE name = ? OR age = ? wrapper.eq("name", "jack") .or() .eq("age", 24) .select("id", "name", "age"); List users = this.userMapper.selectList(wrapper); 6. ActiveRecord ActiveRecord(简称AR)直受动态语( PHP 、 Ruby 等)的喜爱, Java 作为准静态语,对 于 ActiveRecord 往往只能感叹其优雅,所以我们也在 AR 道路上进了定的探索,希望家能够喜 欢。 什么是ActiveRecord? ActiveRecord也属于ORM(对象关系映射)层,由Rails最早提出,遵循标准的ORM模型:表映 射到记录,记录映射到对象,字段映射到对象属性。配合遵循的命名和配置惯例,能够很程度 的快速实现模型的操作,且简洁易懂。 ActiveRecord的主要思想是: 每个数据库表对应创建个类,类的每个对象实例对应于数据库中表的记录;通常 表的每个字段在类中都有相应的Field; ActiveRecord同时负责把持久化,在ActiveRecord中封装了对数据库的访问,即 CURD;; ActiveRecord是种领域模型(Domain Model),封装了部分业务逻辑; 6.1、开启AR之旅 在MP中,开启AR常简单,只需要将实体对象继承Model即可。 for (User user : users) { System.out.println(user); } } } package com.lagou.mp.pojo; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.extension.activerecord.Model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class User extends Model { 6.2、根据主键查询 6.3、新增数据 private Long id; private String userName; private String password; private String name; private Integer age; private String email; } @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testAR() { User user = new User(); user.setId(2L); User user2 = user.selectById(); System.out.println(user2); } } @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testARInsert() { User user = new User(); user.setName("应颠"); user.setAge(30); user.setEmail("yingdian@lagou.cn"); boolean insert = user.insert(); 结果: 6.4、更新操作 结果: System.out.println(insert); } } [main] [com.lagou.mp.mapper.UserMapper.insert]-[DEBUG] ==> Preparing: INSERT INTO tb_user ( name, age, email ) VALUES ( ?, ?, ?, ?, ? ) [main] [com.lagou.mp.mapper.UserMapper.insert]-[DEBUG] ==> Parameters: 应癫 (String), 30(Integer), liubei@lagou.cn(String) [main] [com.lagou.mp.mapper.UserMapper.insert]-[DEBUG] <== Updates: 1 @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testAR() { User user = new User(); user.setId(8L); user.setAge(35); boolean update = user.updateById(); System.out.println(update); } } 6.5、删除操作 结果: [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] ==> Preparing: UPDATE tb_user SET age=? WHERE id=? [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] ==> Parameters: 35(Integer), 8(Long) [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] <== Updates: 1 @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testAR() { User user = new User(); user.setId(7L); boolean delete = user.deleteById(); System.out.println(delete); } } [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] ==> Preparing: DELETE FROM tb_user WHERE id=? [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] ==> Parameters: 7(Long) [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] <== Updates: 1 6.6、根据条件查询 结果: 7. 插件 7.1、mybatis的插件机制 MyBatis 允许你在已映射语句执过程中的某点进拦截调。默认情况下,MyBatis 允许使插件 来拦截的法调包括: 1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) 2. ParameterHandler (getParameterObject, setParameters) 3. ResultSetHandler (handleResultSets, handleOutputParameters) 4. StatementHandler (prepare, parameterize, batch, update, query) 我们看到了可以拦截Executor接的部分法,如update,query,commit,rollback等法,还有 其他接的些法等。 总体概括为: 1. 拦截执器的法 @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testARFindById() { User user = new User(); QueryWrapper userQueryWrapper = new QueryWrapper<>(); userQueryWrapper.le("age","20"); List users = user.selectList(userQueryWrapper); for (User user1 : users) { System.out.println(user1); } } } User(id=1, name=Jone, age=18, email=test1@baomidou.com) User(id=2, name=Jack, age=20, email=test2@baomidou.com) User(id=7, name=慕, age=18, email=test@lagou.cn) 2. 拦截参数的处理 3. 拦截结果集的处理 4. 拦截Sql语法构建的处理 拦截器示例: 注到Spring容器: 或者通过xml配置,mybatis-config.xml: package com.lagou.mp.plugins; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; import java.util.Properties; @Intercepts({@Signature( type= Executor.class, method = "update", args = {MappedStatement.class,Object.class})}) public class MyInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { //拦截法,具体业务逻辑编写的位置 return invocation.proceed(); } @Override public Object plugin(Object target) { //创建target对象的代理对象,的是将当前拦截器加到该对象中 return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { //属性设置 } } /** * 定义拦截器 */ @Bean public MyInterceptor myInterceptor(){ return new MyInterceptor(); } 7.2、执分析插件 在MP中提供了对SQL执的分析的插件,可作阻断全表更新、删除的操作,注意:该插件仅适于开 发环境,不适于产环境。 SpringBoot配置: 测试: 结果:  @Bean public SqlExplainInterceptor sqlExplainInterceptor(){ SqlExplainInterceptor sqlExplainInterceptor = new SqlExplainInterceptor(); List sqlParserList = new ArrayList<>(); // 攻击 SQL 阻断解析器、加解析链 sqlParserList.add(new BlockAttackSqlParser()); sqlExplainInterceptor.setSqlParserList(sqlParserList); return sqlExplainInterceptor; } @Test public void testUpdate(){ User user = new User(); user.setAge(20); int result = this.userMapper.update(user, null); System.out.println("result = " + result); } 可以看到,当执全表更新时,会抛出异常,这样有效防了些误操作。 7.3、性能分析插件 性能分析拦截器,于输出每条 SQL 语句及其执时间,可以设置最执时间,超过时间会抛出异 常。 该插件只于开发环境,不建议产环境使。 配置: javaconfig式 Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of table update operation at com.baomidou.mybatisplus.core.toolkit.ExceptionUtils.mpe(ExceptionUtils.java:4 9) at com.baomidou.mybatisplus.core.toolkit.Assert.isTrue(Assert.java:38) at com.baomidou.mybatisplus.core.toolkit.Assert.notNull(Assert.java:72) at com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser.processUpdate( BlockAttackSqlParser.java:45) at com.baomidou.mybatisplus.core.parser.AbstractJsqlParser.processParser(Abstract JsqlParser.java:92) at com.baomidou.mybatisplus.core.parser.AbstractJsqlParser.parser(AbstractJsqlPar ser.java:67) at com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler.sqlParser (AbstractSqlParserHandler.java:76) at com.baomidou.mybatisplus.extension.plugins.SqlExplainInterceptor.intercept(Sql ExplainInterceptor.java:63) at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61) at com.sun.proxy.$Proxy70.update(Unknown Source) at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession. java:197) ... 41 more xml式 执结果: 可以看到,执时间为11ms。如果将maxTime设置为1,那么,该操作会抛出异常。 @Bean public PerformanceInterceptor performanceInterceptor(){ PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor(); performanceInterceptor.setMaxTime(100); performanceInterceptor.setFormat(true); return performanceInterceptor; }    Time:11 ms - ID:com.lagou.mp.mapper.UserMapper.selectById Execute SQL: SELECT id, user_name, password, name, age, email FROM tb_user WHERE id=7 7.4、乐观锁插件 7.4.1、主要适场景 意图: 当要更新条记录的时候,希望这条记录没有被别更新 乐观锁实现式: 取出记录时,获取当前version 更新时,带上这个version 执更新时, set version = newVersion where version = oldVersion 如果version不对,就更新失败 7.4.2、插件配置 spring xml: spring boot: 7.4.3、注解实体字段 需要为实体字段添加@Version注解。 第步,为表添加version字段,并且设置初始值为1: Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: The SQL execution time is too large, please optimize ! at com.baomidou.mybatisplus.core.toolkit.ExceptionUtils.mpe(ExceptionUtils.java:4 9) at com.baomidou.mybatisplus.core.toolkit.Assert.isTrue(Assert.java:38) ................ @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor() { return new OptimisticLockerInterceptor(); } 第步,为User实体对象添加version字段,并且添加@Version注解: 7.4.4、测试 测试例: 执志: ALTER TABLE `tb_user` ADD COLUMN `version` int(10) NULL AFTER `email`; UPDATE `tb_user` SET `version`='1'; @Version private Integer version; @Test public void testUpdate(){ User user = new User(); user.setAge(30); user.setId(2L); user.setVersion(1); //获取到version为1 int result = this.userMapper.updateById(user); System.out.println("result = " + result); } main] [com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser]- [DEBUG] Original SQL: UPDATE tb_user SET age=?, version=? WHERE id=? AND version=? [main] [com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser]- [DEBUG] parser sql: UPDATE tb_user SET age = ?, version = ? WHERE id = ? AND version = ? [main] [org.springframework.jdbc.datasource.DataSourceUtils]-[DEBUG] Fetching JDBC Connection from DataSource [main] [org.mybatis.spring.transaction.SpringManagedTransaction]-[DEBUG] JDBC Connection [HikariProxyConnection@540206885 wrapping com.mysql.jdbc.JDBC4Connection@27e0f2f5] will not be managed by Spring [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] ==> Preparing: UPDATE tb_user SET age=?, version=? WHERE id=? AND version=? [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] ==> Parameters: 30(Integer), 2(Integer), 2(Long), 1(Integer) [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] <== Updates: 1 [main] [org.mybatis.spring.SqlSessionUtils]-[DEBUG] Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@30135202] result = 1 可以看到,更新的条件中有version条件,并且更新的version为2。 如果再次执,更新则不成功。这样就避免了多同时更新时导致数据的不致。 7.4.5、特别说明 持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime 整数类型下 newVersion = oldVersion + 1 newVersion 会回写到 entity 中 仅持 updateById(id) 与 update(entity, wrapper) 法 在 update(entity, wrapper) 法下, wrapper 不能复!!! 8. Sql 注器 我们已经知道,在MP中,通过AbstractSqlInjector将BaseMapper中的法注到了Mybatis容器,这 样这些法才可以正常执。 那么,如果我们需要扩充BaseMapper中的法,该如何实现呢? 下我们以扩展findAll法为例进学习。 8.1、编写MyBaseMapper 其他的Mapper都可以继承该Mapper,这样实现了统的扩展。 如: 8.2、编写MySqlInjector package com.lagou.mp.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import java.util.List; public interface MyBaseMapper extends BaseMapper { List findAll(); } package com.lagou.mp.mapper; import com.lagou.mp.pojo.User; public interface UserMapper extends MyBaseMapper { User findById(Long id); } 如果直接继承AbstractSqlInjector的话,原有的BaseMapper中的法将失效,所以我们选择继承 DefaultSqlInjector进扩展。 8.3、编写FindAll package com.lagou.mp.sqlInjector; import com.baomidou.mybatisplus.core.injector.AbstractMethod; import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector; import java.util.List; public class MySqlInjector extends DefaultSqlInjector { @Override public List getMethodList() { List methodList = super.getMethodList(); methodList.add(new FindAll()); // 再扩充定义的法 list.add(new FindAll()); return methodList; } } package com.lagou.mp.sqlInjector; import com.baomidou.mybatisplus.core.enums.SqlMethod; import com.baomidou.mybatisplus.core.injector.AbstractMethod; import com.baomidou.mybatisplus.core.metadata.TableInfo; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlSource; public class FindAll extends AbstractMethod { @Override public MappedStatement injectMappedStatement(Class mapperClass, Class modelClass, TableInfo tableInfo) { String sqlMethod = "findAll"; String sql = "select * from " + tableInfo.getTableName(); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); return this.addSelectMappedStatement(mapperClass, sqlMethod, sqlSource, modelClass, tableInfo); } 8.4、注册到Spring容器 8.5、测试 输出的SQL: 此,我们实现了全局扩展SQL注器。 9. 动填充功能 有些时候我们可能会有这样的需求,插或者更新数据时,希望有些字段可以动填充数据,如密 码、version等。在MP中提供了这样的功能,可以实现动填充。 9.1、添加@TableField注解 为email添加动填充功能,在新增数据时有效。 FieldFill提供了多种模式选择: } /** * 定义SQL注器 */ @Bean public MySqlInjector mySqlInjector(){ return new MySqlInjector(); } @Test public void testFindAll(){ List users = this.userMapper.findAll(); for (User user : users) { System.out.println(user); } } [main] [com.lagou.mp.mapper.UserMapper.findAll]-[DEBUG] ==> Preparing: select * from tb_user [main] [com.lagou.mp.mapper.UserMapper.findAll]-[DEBUG] ==> Parameters: [main] [com.lagou.mp.mapper.UserMapper.findAll]-[DEBUG] <== Total: 10 @TableField(fill = FieldFill.INSERT) //插数据时进填充 private String version; public enum FieldFill { 9.2、编写MyMetaObjectHandler 9.3、测试 /** * 默认不处理 */ DEFAULT, /** * 插时填充字段 */ INSERT, /** * 更新时填充字段 */ UPDATE, /** * 插和更新时填充字段 */ INSERT_UPDATE } package com.lagou.mp.handler; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component; @Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { Object password = getFieldValByName("version", metaObject); if(null == password){ //字段为空,可以进填充 setFieldValByName("version", "123456", metaObject); } } @Override public void updateFill(MetaObject metaObject) { } } 结果: 10. 逻辑删除 开发系统时,有时候在实现功能时,删除操作需要实现逻辑删除,所谓逻辑删除就是将数据标记为删 除,并真正的物理删除(DELETE操作),查询时需要携带状态条件,确保被标记的数据不被查 询到。这样做的的就是避免数据被真正的删除。 MP就提供了这样的功能,便我们使,接下来我们起学习下。 10.1、修改表结构 为tb_user表增加deleted字段,于表示数据是否被删除,1代表删除,0代表未删除。 同时,也修改User实体,增加deleted属性并且添加@TableLogic注解: 10.2、配置 @Test public void testInsert(){ User user = new User(); user.setName("冰冰"); user.setAge(30); user.setVersion(1); int result = this.userMapper.insert(user); System.out.println("result = " + result); } ALTER TABLE `tb_user` ADD COLUMN `deleted` int(1) NULL DEFAULT 0 COMMENT '1代表删除,0代表未删除' AFTER `version`; @TableLogic private Integer deleted; application.properties: 10.3、测试 执的SQL: 测试查询: 执的SQL: # 逻辑已删除值(默认为 1) mybatis-plus.global-config.db-config.logic-delete-value=1 # 逻辑未删除值(默认为 0) mybatis-plus.global-config.db-config.logic-not-delete-value=0 @Test public void testDeleteById(){ this.userMapper.deleteById(2L); } [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] ==> Preparing: UPDATE tb_user SET deleted=1 WHERE id=? AND deleted=0 [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] ==> Parameters: 2(Long) [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] <== Updates: 1 @Test public void testSelectById(){ User user = this.userMapper.selectById(2L); System.out.println(user); } 可,已经实现了逻辑删除。 ### 11. 代码成器 AutoGenerator 是 MyBatis-Plus 的代码成器,通过 AutoGenerator 可以快速成 Entity、 Mapper、Mapper XML、Service、Controller 等各个模块的代码,极的提升了开发效率。 11.1、创建程 pom.xml: [main] [com.lagou.mp.mapper.UserMapper.selectById]-[DEBUG] ==> Preparing: SELECT id,user_name,password,name,age,email,version,deleted FROM tb_user WHERE id=? AND deleted=0 [main] [com.lagou.mp.mapper.UserMapper.selectById]-[DEBUG] ==> Parameters: 2(Long) [main] [com.lagou.mp.mapper.UserMapper.selectById]-[DEBUG] <== Total: 0  4.0.0 org.springframework.boot spring-boot-starter-parent 2.3.4.RELEASE  com.lagou lagou-mp-generator 0.0.1-SNAPSHOT lagou-mp-generator Demo project for Spring Boot 11 org.springframework.boot spring-boot-starter-test test  com.baomidou mybatis-plus-boot-starter 3.1.1 com.baomidou mybatis-plus-generator 3.1.1 org.springframework.boot spring-boot-starter-freemarker  mysql mysql-connector-java 5.1.47 org.slf4j slf4j-log4j12 org.springframework.boot spring-boot-starter-web org.projectlomboklomboktrueorg.springframework.bootspring-boot-maven-plugin 11.2、代码 package com.lagou.mp.generator; import java.util.ArrayList; import java.util.List; import java.util.Scanner; import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException; import com.baomidou.mybatisplus.core.toolkit.StringPool; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.generator.AutoGenerator; import com.baomidou.mybatisplus.generator.InjectionConfig; import com.baomidou.mybatisplus.generator.config.DataSourceConfig; import com.baomidou.mybatisplus.generator.config.FileOutConfig; import com.baomidou.mybatisplus.generator.config.GlobalConfig; import com.baomidou.mybatisplus.generator.config.PackageConfig; import com.baomidou.mybatisplus.generator.config.StrategyConfig; import com.baomidou.mybatisplus.generator.config.TemplateConfig; import com.baomidou.mybatisplus.generator.config.po.TableInfo; import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; /** *

*MySQL code generator example*

*/ public class MysqlGenerator { /** *
*/ public class MysqlGenerator { /** *

*Read console content*

*/ public static String scanner(String tip) { Scanner scanner = new Scanner(System.in); StringBuilder help = new StringBuilder(); help.append("请输" + tip + ":"); System.out.println(help.toString()); if (scanner.hasNext()) { String ipt = scanner.next(); if (StringUtils.isNotEmpty(ipt)) { return ipt; } } throw new MybatisPlusException("请输正确的" + tip + "!"); } /** * RUN THIS */ public static void main(String[] args) { // 代码成器 AutoGenerator mpg = new AutoGenerator(); // 全局配置 GlobalConfig gc = new GlobalConfig(); String projectPath = System.getProperty("user.dir"); gc.setOutputDir(projectPath + "/src/main/java"); gc.setAuthor("lagou"); gc.setOpen(false); mpg.setGlobalConfig(gc); // 数据源配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://127.0.0.1:3306/mp? useUnicode=true&useSSL=false&characterEncoding=utf8"); // dsc.setSchemaName("public"); dsc.setDriverName("com.mysql.jdbc.Driver"); dsc.setUsername("root"); dsc.setPassword("root"); mpg.setDataSource(dsc); // 包配置 PackageConfig pc = new PackageConfig(); pc.setModuleName(scanner("模块名")); pc.setParent("com.lagou.mp.generator"); mpg.setPackageInfo(pc); // 定义配置 InjectionConfig cfg = new InjectionConfig() { @Override public void initMap() { // to do nothing } }; List focList = new ArrayList<>(); focList.add(new FileOutConfig("/templates/mapper.xml.ftl") { @Override public String outputFile(TableInfo tableInfo) { // 定义输件名称 11.3、测试 return projectPath + "/lagou-mpgenerator/src/main/resources/mapper/" + pc.getModuleName() + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML; } }); cfg.setFileOutConfigList(focList); mpg.setCfg(cfg); mpg.setTemplate(new TemplateConfig().setXml(null)); // 策略配置 StrategyConfig strategy = new StrategyConfig(); strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setColumnNaming(NamingStrategy.underline_to_camel); // strategy.setSuperEntityClass("com.baomidou.mybatisplus.samples.generator.commo n.BaseEntity"); strategy.setEntityLombokModel(true); // strategy.setSuperControllerClass("com.baomidou.mybatisplus.samples.generator.c ommon.BaseController"); strategy.setInclude(scanner("表名")); strategy.setSuperEntityColumns("id"); strategy.setControllerMappingHyphenStyle(true); strategy.setTablePrefix(pc.getModuleName() + "_"); mpg.setStrategy(strategy); // 选择 freemarker 引擎需要指定如下加,注意 pom 依赖必须有! mpg.setTemplateEngine(new FreemarkerTemplateEngine()); mpg.execute(); } } 代码已成: 实体对象: 12. MybatisX 快速开发插件 MybatisX 是款基于 IDEA 的快速开发插件,为效率。 安装法:打开 IDEA,进 File -> Settings -> Plugins -> Browse Repositories,输 mybatisx 搜 索并安装。 功能: Java 与 XML 调回跳转 Mapper 法动成 XML