mybatis

Mybatis

MyBatis官网地址:http://www.mybatis.org/mybatis-3/

第一部分 自定持久层框架

1.1 初始化数据库

-- 用户表和记录
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
​
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `password` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `birthday` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
​
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'lucy', '123', '2019-12-12');
INSERT INTO `user` VALUES (2, 'tom', '123', '2019-12-12');
INSERT INTO `user` VALUES (3, 'jack', '123456', '2020-12-02');
​
SET FOREIGN_KEY_CHECKS = 1;

1.2 分析JDBC操作问题

package com.topxin.test;
import com.topxin.pojo.User;
import java.sql.*;
public class Main {
    public static void main(String[] args) throws SQLException {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try{
            //1.获取驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2.通过驱动获取数据库连接
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/zdy_mybatis?characterEncoding=utf-8","root","root");
            //3.定义sql语句
            String sql = "select * from user where username = ?";
            //4.获取预处理statement对象
            preparedStatement = connection.prepareStatement(sql);
            //5.设置参数
            preparedStatement.setString(1,"tom");
            //6.查询
            resultSet  = preparedStatement.executeQuery();
            //7.遍历结果集合
            while (resultSet.next()){
                int id = resultSet.getInt("id");
                String username = resultSet.getString("username");
                //8.封装user
                User user = new User();
                user.setId(id);
                user.setUsername(username);
                System.out.println(user);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //9.释放资源
            if (resultSet != null) {
                resultSet.close();
            }
            if(preparedStatement != null){
                preparedStatement.close();
            }
           if(connection != null){
               connection.close();
           }
        }
    }
}

JDBC问题总结:

原始jdbc开发存在的问题如下:

1、 数据库连接创建、释放频繁造成系统资源浪费,从而影响系统性能。

2、 Sql语句在代码中硬编码,造成代码不易维护,实际应用中sql变化的可能较大,sql变动需要改变java代码。

3、 使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。

4、 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便

1.3 问题解决思路

①使用数据库连接池初始化连接资源

②将sql语句抽取到xml配置文件中

③使用反射、内省等底层技术,自动将实体与表进行属性与字段的自动映射

1.4 自定义持久层框架设计思路

使用端:

提供核心配置文件:

sqlMapConfig.xml : 存放数据源信息,引入mapper.xml

Mapper.xml : sql语句的配置文件信息

框架端:

1.读取配置文件

读取完成以后以流的形式存在,我们不能将读取到的配置信息以流的形式存放在内存中,不好操作,可

以创建javaBean来存储

(1)Configuration : 存放数据库基本信息、Map<唯一标识,Mapper> 唯一标识:namespace + "."+ id

(2)MappedStatement:sql语句、statement类型、输入参数java类型、输出参数java类型

2.解析配置文件

创建sqlSessionFactoryBuilder类:

方法:sqlSessionFactory build():

第一:使用dom4j解析配置文件,将解析出来的内容封装到Configuration和MappedStatement中

第二:创建SqlSessionFactory的实现类DefaultSqlSession

3.创建SqlSessionFactory:

方法:openSession() : 获取sqlSession接口的实现类实例对象

4.创建sqlSession接口及实现类:主要封装crud方法

方法:selectList(String statementId,Object param):查询所有

selectOne(String statementId,Object param):查询单个

具体实现:封装JDBC完成对数据库表的查询操作

涉及到的设计模式:

Builder构建者设计模式、工厂模式、代理模式

1.5 自定义框架实现

1.4.1 pom.xml

<dependencies>
    <!--需要操作数据库-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.17</version>
    </dependency>
    <!--数据库连接池-->
    <dependency>
        <groupId>c3p0</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.1.2</version>
    </dependency>
    <!--日志-->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.8</version>
    </dependency>
    <!--测试-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!--解析XML配置文件-->
    <dependency>
        <groupId>dom4j</groupId>
        <artifactId>dom4j</artifactId>
        <version>1.6.1</version>
    </dependency>
    <dependency>
        <groupId>jaxen</groupId>
        <artifactId>jaxen</artifactId>
        <version>1.1.6</version>
    </dependency>
</dependencies>

1.4.2 配置文件(核心)

sqlMapperConfig.xml

<configuration>
    <!--数据配置信息-->
    <dataSource>
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/zdy_mybatis?characterEncoding=utf-8"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </dataSource>
    <!--  存放mapper.xml的路径 -->
    <mapper resource="UserMapper.xml"></mapper>
</configuration>

UserMapper.xml

<mapper namespace="com.topxin.dao.IUserDao">
    <!-- sql 的唯一标识:namespace.id组成:statementId-->
    <select id="findAll" resultType="com.topxin.pojo.User">
        select * from user
    </select>
    <!--
        User user = new User();
        user.setId(1);
        user.setUsernmae("zhangsan");
    -->
    <select id="findByCondition" resultType="com.topxin.pojo.User" parameterType="com.topxin.pojo.User">
        select * from user where id = #{id} and username = #{username}
    </select>
</mapper>

1.4.3 创建实体

Configuration

public class Configuration {
    //连接数据库信息封装
    private DataSource dataSource;   
    //key:statementId value:封装好的mappedStatement对象
    Map<String,MapperStatement> mapperStatementMap = new HashMap<String, MapperStatement>();
    ...get/set
}

MapperStatement

public class MapperStatement {
    private String id;    //id标识
    private String resultType;    //返回值类型
    private String parameterType;   //参数值类型
    private String sql;    //sql语句
    ...get/set
}

User

public class User {
    private Integer id;
    private String username;
    private String password;
    private String birthday;
    ...get/set
}

1.4.4 读取解析封装

Resource

读取:Resource获取配置文件流

package com.topxin.io;
​
import java.io.InputStream;
​
public class Resource {
    //根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中
    public static InputStream getResourceAsSteam(String path){
        InputStream resourceAsStream = Resource.class.getClassLoader().getResourceAsStream(path);
        return resourceAsStream;
    }
}

XMlConfigBuilder

解析:将SqlMapperConfig.xml配置文件通过dom4j解析

封装:Configuration对象

注意:Configuration对象同时封装MapperStatement

package com.topxin.config;
​
import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.topxin.io.Resource;
import com.topxin.pojo.Configuration;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
​
import java.beans.PropertyVetoException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;
​
public class XMlConfigBuilder {
​
    private Configuration configuration;
​
    public XMlConfigBuilder() {
        this.configuration = new Configuration();
    }
​
    /**
     * 该方法即使将配置文件进行解析,封装Configuration
     * @return
     */
    public Configuration parseConfig(InputStream inputStream) throws PropertyVetoException, DocumentException {
        Document document = new SAXReader().read(inputStream);
        //configuration
        Element rootElement = document.getRootElement();
        Properties properties = new Properties();
        List<Element> list = rootElement.selectNodes("//property");
        for (Element element : list) {
            String name = element.attributeValue("name");
            String value = element.attributeValue("value");
            properties.setProperty(name,value);
        }
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
        comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
        comboPooledDataSource.setUser(properties.getProperty("username"));
        comboPooledDataSource.setPassword(properties.getProperty("password"));
        configuration.setDataSource(comboPooledDataSource);
        //mapper.xml解析,拿到路径,根据路径---字节输入六---dom4j解析
        List<Element> mapperList = rootElement.selectNodes("//mapper");
        for (Element element : mapperList) {
            String mapperPath = element.attributeValue("resource");
            InputStream resourceAsSteam = Resource.getResourceAsSteam(mapperPath);
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
            xmlMapperBuilder.parse(resourceAsSteam);
        }
        return configuration;
    }
}

XMLMapperBuilder

解析:将UserMapper.xml配置文件通过dom4j解析

封装:MapperStatement对象

注意:根据 statementId的key = namespace +"."+ id

package com.topxin.config;
​
import com.topxin.pojo.Configuration;
import com.topxin.pojo.MapperStatement;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
​
import java.io.InputStream;
import java.util.List;
​
public class XMLMapperBuilder {
​
    private Configuration configuration;
​
    public XMLMapperBuilder(Configuration configuration) {
        this.configuration = configuration;
    }
​
    public void  parse(InputStream inputStream) throws DocumentException {
        Document document = new SAXReader().read(inputStream);
        Element rootElement = document.getRootElement();
        String namespace = rootElement.attributeValue("namespace");
        List<Element> list = rootElement.selectNodes("//select");
        for (Element element : list) {
            String id = element.attributeValue("id");
            String resultType = element.attributeValue("resultType");
            String paramterType = element.attributeValue("parameterType");
            String sqlText = element.getTextTrim();
            MapperStatement mapperStatement = new MapperStatement();
            mapperStatement.setId(id);
            mapperStatement.setResultType(resultType);
            mapperStatement.setParameterType(paramterType);
            mapperStatement.setSql(sqlText);
            String key = namespace +"."+ id;
            configuration.getMapperStatementMap().put(key,mapperStatement);
        }
    }
}

1.4.5 SqlSessionFactory

核心方法openSession()

SqlSessionFactoryBuilder

package com.topxin.sqlSession;
​
import com.topxin.config.XMlConfigBuilder;
import com.topxin.pojo.Configuration;
import org.dom4j.DocumentException;
​
import java.beans.PropertyVetoException;
import java.io.InputStream;
​
public class SqlSessionFactoryBuilder {
    public SqlSessionFactory build(InputStream in) throws PropertyVetoException, DocumentException {
        //第一:使用dom4j解析配置文件,将解析出来的内容防撞到Configuration中
        XMlConfigBuilder xMlConfigBuilder = new XMlConfigBuilder();
        Configuration configuration = xMlConfigBuilder.parseConfig(in);
        //第二:创建sqlSessionFactory对象,工厂类,生产sqlSession会话对象
        DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);
        return  defaultSqlSessionFactory;
    }
}

SqlSessionFactory接口

package com.topxin.sqlSession;
​
public interface SqlSessionFactory {
    public SqlSession openSession();
}

SqlSessionFactory接口的实现类DefaultSqlSessionFactory

package com.topxin.sqlSession;
​
import com.topxin.pojo.Configuration;
​
public class DefaultSqlSessionFactory implements  SqlSessionFactory{
​
    private  Configuration configuration;
​
    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }
​
    @Override
    public DefaultSqlSession openSession() {
        return new DefaultSqlSession(configuration);
    }
}

1.4.6 SqlSession

invoke()方法

SqlSession接口

package com.topxin.sqlSession;
​
import java.util.List;
​
public interface SqlSession {
​
    //查询所有
    public <E> List<E> selectList(String statementId,Object... params) throws Exception;
​
    //根据条件查询单个
    public <T> T selectOne(String statementId,Object... params) throws Exception;
​
    //为Dao接口生产代理实现类
    public <T> T  getMapper(Class<?> mapperClass);
}

SqlSession接口的实现类

package com.topxin.sqlSession;
​
import com.topxin.pojo.Configuration;
import com.topxin.pojo.MapperStatement;
​
import java.lang.reflect.*;
import java.util.List;
​
public class DefaultSqlSession implements SqlSession {
​
    private Configuration configuration;
​
    public DefaultSqlSession(Configuration configuration){
        this.configuration = configuration;
    }
​
    @Override
    public <E> List<E> selectList(String statementId, Object... params) throws Exception {
        //SimpleExecutor里的query方法调用
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        MapperStatement mapperStatement = configuration.getMapperStatementMap().get(statementId);
        List<Object> list = simpleExecutor.query(configuration, mapperStatement, params);
        return (List<E>) list;
    }
​
    @Override
    public <T> T selectOne(String statementId, Object... params) throws Exception {
        List<Object> objects = selectList(statementId, params);
        if(objects.size() == 1){
            return  (T) objects.get(0);
        }else {
            throw new RuntimeException("查询结果未空或者返回多条结果");
        }
    }
​
    @Override
    public <T> T getMapper(Class<?> mapperClass) {
        //使用JDK动态代理来为Dao接口涩会给你从代理对象,并返回
        Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
            /**
             * 代理对象调用接口任何方法,都会执行invoke方法
             * @param proxy 当前代理对象的应用
             * @param method  当前被调用的方法引用
             * @param args  传递的参数(user)
             * @return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //注意:底层还是去调用JDBC代码//根据不同情况,来调用selectList或者SelectOne
                //准备参数1:statementId:sql语句的唯一标识:namespace.id = 接口权限定名。方法名
                //方法名
                String methodName = method.getName();
                String className = method.getDeclaringClass().getName();
                String statementId = className +"."+ methodName;
                //准备参数 params:args
                //获取被调用方法的反回值类型
                Type genericReturnType = method.getGenericReturnType();
                //判断是否进行了,泛型类型参数化
                if(genericReturnType instanceof ParameterizedType){
                    List<Object> objects = selectList(statementId, args);
                    return objects;
                }
                return selectOne(statementId,args);
            }
        });
        return (T) proxyInstance;
    }
}

1.4.7 Executor

Executor接口

package com.topxin.sqlSession;
​
import com.topxin.pojo.Configuration;
import com.topxin.pojo.MapperStatement;
​
import java.sql.SQLException;
import java.util.List;
​
public interface Executor {
​
    public <E> List<E> query(Configuration configuration, MapperStatement mapperStatement, Object... params) throws SQLException, Exception;
}

BoundSql封装解析后的Sql语句

package com.topxin.sqlSession;
​
import com.topxin.utils.ParameterMapping;
​
import java.util.ArrayList;
import java.util.List;
​
public class BoundSql {
    private String sqlText;     //解析过后的sql
    private List<ParameterMapping> parameterMappings = new ArrayList<>();
    public BoundSql(String sqlText, List<ParameterMapping> parameterMappings) {
        this.sqlText = sqlText;
        this.parameterMappings = parameterMappings;
    }
    public String getSqlText() {
        return sqlText;
    }
    public void setSqlText(String sqlText) {
        this.sqlText = sqlText;
    }
    public List<ParameterMapping> getParameterMappings() {
        return parameterMappings;
    }
    public void setParameterMappings(List<ParameterMapping> parameterMappings) {
        this.parameterMappings = parameterMappings;
    }
}

SimpleExecutor类实现了Executor接口

package com.topxin.sqlSession;
​
import com.topxin.pojo.Configuration;
import com.topxin.pojo.MapperStatement;
import com.topxin.utils.GenericTokenParser;
import com.topxin.utils.ParameterMapping;
import com.topxin.utils.ParameterMappingTokenHandler;
​
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;
​
public class SimpleExecutor implements  Executor {
​
    @Override
    public <E> List<E> query(Configuration configuration, MapperStatement mapperStatement, Object... params) throws Exception {
        //1.获取连接
        Connection connection = configuration.getDataSource().getConnection();
        //2,获取sql语句,还需要#{}进行转换?
        String sql = mapperStatement.getSql();
        BoundSql boundSql = getBoundSql(sql);
        //3/获取预处理对象
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
        //4.设置参数
        String parameterType = mapperStatement.getParameterType();//获取到参数
        Class<?> parameterTypeClass = getClassType(parameterType);
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        for (int i = 0; i < parameterMappings.size(); i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            String content = parameterMapping.getContent();
            //反射
            Field declaredField = parameterTypeClass.getDeclaredField(content);
            declaredField.setAccessible(true);
            Object o = declaredField.get(params[0]);
            preparedStatement.setObject(i+1,o);     //占位符从1开始
        }
        //5.执行sql
        ResultSet resultSet = preparedStatement.executeQuery();
        String resultType = mapperStatement.getResultType();
        Class<?> resultTypeClass = getClassType(resultType);
        ArrayList<Object> objects = new ArrayList<>();
        //6.分装返回结果集
        while(resultSet.next()){
            Object o = resultTypeClass.newInstance();
            //元数据
            ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                //字段名称
                String columnName = metaData.getColumnName(i);
                //字段的值
                Object value = resultSet.getObject(columnName);
                //使用反射,根据数据库表和实体的对象关系完成封装
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(o,value);
            }
            objects.add(o);
        }
        return (List<E>) objects;
    }
​
    private Class<?> getClassType(String parameterType) throws ClassNotFoundException {
        if(parameterType != null){
            Class<?> aClass = Class.forName(parameterType);
            return  aClass;
        }
        return null;
    }
​
    /**
     * 完成对#{}的解析共工作,1将#{}使用?,进行代替,2解析#{}里面的
     * @param sql
     * @return
     */
    private BoundSql getBoundSql(String sql) {
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        //解析后的sql
        String parseSql = genericTokenParser.parse(sql);
        //#{}里面解析出来的参数名称
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
        BoundSql boundSql = new BoundSql(parseSql,parameterMappings);
        return  boundSql;
    }
}

1.4.8 Utils

GenericTokenParser解析Mybaitis中Sql语句的${}和#{}

/**
 *    Copyright 2009-2017 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package com.topxin.utils;
​
/**
 * @author Clinton Begin
 */
public class GenericTokenParser {
​
  private final String openToken; //开始标记
  private final String closeToken; //结束标记
  private final TokenHandler handler; //标记处理器
​
  public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
    this.openToken = openToken;
    this.closeToken = closeToken;
    this.handler = handler;
  }
​
  /**
   * 解析${}和#{}
   * @param text
   * @return
   * 该方法主要实现了配置文件、脚本等片段中占位符的解析、处理工作,并返回最终需要的数据。
   * 其中,解析工作由该方法完成,处理工作是由处理器handler的handleToken()方法来实现
   */
  public String parse(String text) {
    // 验证参数问题,如果是null,就返回空字符串。
    if (text == null || text.isEmpty()) {
      return "";
    }
​
    // 下面继续验证是否包含开始标签,如果不包含,默认不是占位符,直接原样返回即可,否则继续执行。
    int start = text.indexOf(openToken, 0);
    if (start == -1) {
      return text;
    }
​
   // 把text转成字符数组src,并且定义默认偏移量offset=0、存储最终需要返回字符串的变量builder,
    // text变量中占位符对应的变量名expression。判断start是否大于-1(即text中是否存在openToken),如果存在就执行下面代码
    char[] src = text.toCharArray();
    int offset = 0;
    final StringBuilder builder = new StringBuilder();
    StringBuilder expression = null;
    while (start > -1) {
     // 判断如果开始标记前如果有转义字符,就不作为openToken进行处理,否则继续处理
      if (start > 0 && src[start - 1] == '\\') {
        builder.append(src, offset, start - offset - 1).append(openToken);
        offset = start + openToken.length();
      } else {
        //重置expression变量,避免空指针或者老数据干扰。
        if (expression == null) {
          expression = new StringBuilder();
        } else {
          expression.setLength(0);
        }
        builder.append(src, offset, start - offset);
        offset = start + openToken.length();
        int end = text.indexOf(closeToken, offset);
        while (end > -1) {存在结束标记时
          if (end > offset && src[end - 1] == '\\') {//如果结束标记前面有转义字符时
            // this close token is escaped. remove the backslash and continue.
            expression.append(src, offset, end - offset - 1).append(closeToken);
            offset = end + closeToken.length();
            end = text.indexOf(closeToken, offset);
          } else {//不存在转义字符,即需要作为参数进行处理
            expression.append(src, offset, end - offset);
            offset = end + closeToken.length();
            break;
          }
        }
        if (end == -1) {
          // close token was not found.
          builder.append(src, start, src.length - start);
          offset = src.length;
        } else {
          //首先根据参数的key(即expression)进行参数处理,返回?作为占位符
          builder.append(handler.handleToken(expression.toString()));
          offset = end + closeToken.length();
        }
      }
      start = text.indexOf(openToken, offset);
    }
    if (offset < src.length) {
      builder.append(src, offset, src.length - offset);
    }
    return builder.toString();
  }
}

ParameterMapping分装Mybaitis中Sql语句的${}和#{}的内容

package com.topxin.utils;
​
public class ParameterMapping {
​
    private String content;
​
    public ParameterMapping(String content) {
        this.content = content;
    }
​
    public String getContent() {
        return content;
    }
​
    public void setContent(String content) {
        this.content = content;
    }
}
​

TokenHandler接口

/**
 *    Copyright 2009-2015 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package com.topxin.utils;
​
/**
 * @author Clinton Begin
 */
public interface TokenHandler {
  String handleToken(String content);
}

ParameterMappingTokenHandler类

package com.topxin.utils;
​
import java.util.ArrayList;
import java.util.List;
​
public class ParameterMappingTokenHandler implements TokenHandler {
    private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
​
    // context是参数名称 #{id} #{username}
​
    public String handleToken(String content) {
        parameterMappings.add(buildParameterMapping(content));
        return "?";
    }
​
    private ParameterMapping buildParameterMapping(String content) {
        ParameterMapping parameterMapping = new ParameterMapping(content);
        return parameterMapping;
    }
​
    public List<ParameterMapping> getParameterMappings() {
        return parameterMappings;
    }
​
    public void setParameterMappings(List<ParameterMapping> parameterMappings) {
        this.parameterMappings = parameterMappings;
    }
}

1.4.9 Test测试

package com.topxin.test;
​
import com.topxin.dao.IUserDao;
import com.topxin.io.Resource;
import com.topxin.pojo.User;
import com.topxin.sqlSession.SqlSession;
import com.topxin.sqlSession.SqlSessionFactory;
import com.topxin.sqlSession.SqlSessionFactoryBuilder;
import org.junit.Test;
​
import java.io.InputStream;
import java.util.List;
​
public class IPersistenceTest {
​
    @Test
    public void test() throws Exception {
        InputStream resourceAsSteam = Resource.getResourceAsSteam("sqlMapperConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //调用
        User user = new User();
        user.setId(1);
        user.setUsername("lucy");
       /* User u = sqlSession.selectOne("user.selectOne", user);
        System.out.println(u);*/
        /*List<User> users = sqlSession.selectList("user.selectList");
        for (User u : users) {
            System.out.println(u);
        }*/
        //返回的userDao是代理对象proxy
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);
        /*User byCondition = userDao.findByCondition(user);
        System.out.println(byCondition);*/
        List<User> all = userDao.findAll();
        for (User user1 : all) {
            System.out.println(user1);
        }
    }
}

1.6 自定义框架优化

通过上述我们的自定义框架,我们解决了JDBC操作数据库带来的一些问题:例如频繁创建释放数据库连接,硬编码,手动封装返回结果集等问题,但是现在我们继续来分析刚刚完成的自定义框架代码,有没有什么问题?

问题如下:

  • dao的实现类中存在重复的代码,整个操作的过程模板重复(创建sqlsession,调用sqlsession方法,关闭 sqlsession)

  • dao的实现类中存在硬编码,调用sqlsession的方法时,参数statement的id硬编码

解决:使用代理模式来创建接口的代理对象

@Test 
public void test2() throws Exception { 
    InputStream resourceAsSteam = Resources.getResourceAsSteam(path: "sqlMapConfig.xml")            SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsSteam); 
    SqlSession  sqlSession = build.openSession(); 
    User user = new User(); 
    user.setld(l); 
    user.setUsername("tom"); //代理对象 
    UserMapper userMapper = sqlSession.getMappper(UserMapper.class); 
    User userl = userMapper.selectOne(user); 
    System・out.println(userl);
}

在sqlSession中添加方法

public interface SqlSession { public <T> T getMappper(Class<?> mapperClass);}

实现类

@Override 
public <T> T getMappper(Class<?> mapperClass) { 
    T o = (T) Proxy.newProxyInstance(mapperClass.getClassLoader(), new Class[] {mapperClass}, new InvocationHandler() { 
        @Override 
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // selectOne 
            String methodName = method.getName(); 
            // className:namespace 
            String className = method.getDeclaringClass().getName(); 
            //statementid 
            String key = className+"."+methodName; 
            MappedStatement mappedStatement = configuration.getMappedStatementMap().get(key);
            Type genericReturnType = method.getGenericReturnType(); 
            ArrayList arrayList = new ArrayList<> (); 
            //判断是否实现泛型类型参数化 
            if(genericReturnType instanceof ParameterizedType){ 
                return selectList(key,args); 
                return selectOne(key,args); 
            } 
    }); 
    return o;
}

第二部分 Mybatis相关概念

2.1 对象/关系数据映射ORM

Object Relation Mapping

既可以利用面向对象程序设计语言的简单易用性,又可以利用关系数据库的技术优势

2.2 Mybatis简介

ORM、半自动、轻量级框架

支持定制化sql,存储过程,高级映射

xml或者注解配置和映射原生类

2.3 Mybaits历史

原是apache的一个开源项目iBatis, 2010年6月这个项目由apache software foundation 迁移到了

google code,随着开发团队转投Google Code旗下,ibatis3.x正式更名为Mybatis ,代码于2013年11

月迁移到Github。

iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框

架包括SQL Maps和Data Access Objects(DAO)

2.4 Mybaits优势

半自动的持久层框架(轻量级),核心Sql可以自己优化

sql和Java代码分离(功能边界清晰,一个专注业务,一个专注数据)

第三部分 Mybatis基础应用

3.1 快速入门

3.1.1 导入GAV坐标

<dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
</dependencies>

3.1.2 创建数据库表

user表,创建语句见1.1

3.1.3 创建对应实体

User

3.1.4 编写映射文件

UserMapper.xml

3.1.5 编写核心配置文件

SqlMapConfig.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--应用启动,解析配置文件生产Configuration配置对象
        environments:default属性选择项目当前数据源值就是environment的id
        environment数据库源,可以配置多个-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" /><!--事务管理JDBC-->
            <dataSource type="POOLED"><!--mybatis自带-->
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/zdy_mybatis?characterEncoding=utf8" />
                <property name="username" value="root" />
                <property name="password" value="root" />
            </dataSource>
        </environment>
    </environments>
​
    <mappers>
        <!--单个mapper配置,-->
        <mapper resource="UserMapper.xml"></mapper>
    </mappers>
</configuration>

3.1.6 编写测试方法

@Test
public void testMybatis() throws IOException {
        //加载核心配置文件,返回流
        InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        //获取sqlSession工厂
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //获取sqlSession对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        List<User> list = sqlSession.selectList("userMapper.queryUserList");
        if(list != null && list.size() > 0) {
            for (int i = 0; i < list.size(); i++) {
                User user =  list.get(i);
                System.out.println(user);
            }
        }
        //关闭资源
        sqlSession.close();
}

3.2 MyBatis常用配置解析

3.2.1 environments标签

 

其中,事务管理器(transactionManager)类型有两种:

  • JDBC:这个配置就是直接使用了JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域。

  • MANAGED:这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接,然而一些容器并不希望这样,因此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为。

其中,数据源(dataSource)类型有三种:

  • UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。

  • POOLED:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来。

  • JNDI:这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。

3.2.2 mapper标签

该标签的作用是加载映射的,加载方式有如下几种:

  • 使用相对于类路径的资源引用,例如:

<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>

  • 使用完全限定资源定位符(URL),例如:

<mapper url="file:///var/mappers/AuthorMapper.xml"/>

  • 使用映射器接口实现类的完全限定类名,例如:

<mapper class="org.mybatis.builder.AuthorMapper"/>

  • 将包内的映射器接口实现全部注册为映射器,例如:

<package name="org.mybatis.builder"/>

3.3 Mybatis相关API介绍

SqlSession工厂构建器SqlSessionFactoryBuilder

常用API:SqlSessionFactory build(InputStream inputStream)

通过加载mybatis的核心文件的输入流的形式构建一个SqlSessionFactory对象

String resource = "org/mybatis/builder/mybatis-config.xml"; 
InputStream inputStream = Resources.getResourceAsStream(resource); 
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); 
SqlSessionFactory factory = builder.build(inputStream);

其中, Resources 工具类,这个类在 org.apache.ibatis.io 包中。Resources 类帮助你从类路径下、文

件系统或一个 web URL 中加载资源文件。

SqlSession工厂对象SqlSessionFactory

SqlSessionFactory 有多个个方法创建SqlSession 实例。常用的有如下两个:

方法解释
openSession()会默认会默认开启一个事务,但事物不会自动提交,也就意味需要手动提交改事物,更新操作数据库才会吃计划到数据库中
openSession(boolean autoCommit)参数是否自动提交,如果设置未true,那么不需要手动提交事务

执行语句的方法主要有:

<T> T selectOne(String statement, Object parameter) 
<E> List<E> selectList(String statement, Object parameter) 
int insert(String statement, Object parameter) 
int update(String statement, Object parameter) 
int delete(String statement, Object parameter)

操作事务的方法主要有:

void commit() 
void rollback()

3.4 Mybaits的Dao层实现

3.4.1 传统开发方式

3.4.2 代理开发方式

采用 Mybatis 的代理开发方式实现 DAO 层的开发,这种方式是我们后面进入企业的主流。

Mapper 接口开发方法只需要程序员编写Mapper 接口(相当于Dao 接口),由Mybatis 框架根据接口

定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。

Mapper 接口开发需要遵循以下规范:

1) Mapper.xml文件中的namespace与mapper接口的全限定名相同

2) Mapper接口方法名和Mapper.xml中定义的每个statement的id*相同

3) Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同

4) Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同

 

第四部分 配置文件深入

4.1 SqlMapConfiig.xml

4.1.1 配置

 

4.1.2 environments环境配置

事务管理器(transactionManager)

在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):

  • JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。

  • MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。例如:

数据源(dataSource)

dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):

UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。

POOLED(推荐)– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。

JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。

4.1.3 mapper映射器

使用相对于类路径的资源引用

使用完全限定资源定位符(URL)

使用映射器接口实现类的完全限定类名

<!-- 将包内的映射器接口实现全部注册为映射器 --> 
<mappers>
    <package name="com.topxin.mapper"/>
</mappers>

4.1.4 properties属性

<properties>
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/zdy_mybatis?characterEncoding=utf8"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
</properties>

从 MyBatis 3.4.2 开始,你可以为占位符指定一个默认值。例如:

<dataSource type="POOLED">
  <!-- ... -->
  <property name="username" value="${username:ut_user}"/> <!-- 如果属性 'username' 没有被配置,'username' 属性的值将为 'ut_user' -->
</dataSource>

4)typeAliases类型别名标签

<!-- 定义类型别名,单独指定 -->
<typeAliases>
    <typeAlias alias="User" type="com.topxin.User"/>
    <typeAlias alias="Orders" type="com.topxin.Orders"/>
</typeAliases>
<!-- 定义类型别名,扫描指定的包,这样扫描的别名默认是类名首字母小写,如果需要单独指定可以使用@Alias注解,使用方式如下: -->
<typeAliases>
  <package name="com.topxin"/>
</typeAliases>
// 扫描包方式自定义别名
@Alias("user")
public class user {...}
别名映射的类型
_bytebyte
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
objectObject
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection
iteratorIterator

4.2 Mapper.xml

4.2.1 动态sql

  • if

  • choose (when, otherwise)

  • trim (where, set)

  • foreach

  • sql

第五部分 Mybatis复杂映射

5.1 一对一

一个用户对应一个订单

SELECT * FROM orders o INNER JOIN USER u ON o.uid = u.id

resultMap:手动配置

association:一对一

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.topxin.mapper.OrdersMapper">
    <!--自定义映射关系-->
    <resultMap id="orderAndUserResultMap" type="com.topxin.pojo.Orders">
        <id property="id" column="id" />
        <result property="ordertime" column="ordertime" />
        <result property="total" column="total" />
        <!-- user映射 -->
        <association property="user" javaType="com.topxin.pojo.User">
            <result property="id" column="uid" />
            <result property="username" column="username" />
            <result property="password" column="password" />
            <result property="birthday" column="birthday" />
        </association>
    </resultMap>
    <!--一对一查询-->
    <select id="findOrderAndUser" resultMap="orderAndUserResultMap">
        SELECT * FROM orders o INNER JOIN USER u ON o.uid = u.id
    </select>
</mapper>

订单表初始化

-- 创建订单表并插入记录
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
​
-- ----------------------------
-- Table structure for orders
-- ----------------------------
DROP TABLE IF EXISTS `orders`;
CREATE TABLE `orders`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `ordertime` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `total` double NULL DEFAULT NULL,
  `uid` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `uid`(`uid`) USING BTREE,
  CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
​
-- ----------------------------
-- Records of orders
-- ----------------------------
INSERT INTO `orders` VALUES (1, '2019-12-12', 3000, 1);
INSERT INTO `orders` VALUES (2, '2019-12-12', 4000, 1);
INSERT INTO `orders` VALUES (3, '2019-12-12', 5000, 2);
​
SET FOREIGN_KEY_CHECKS = 1;

5.2 一对多

一个用户对应多个订单

SELECT * FROM USER u LEFT JOIN orders o ON u.id = o.uid

collection:一对多

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.topxin.mapper.UserMapper">
    <resultMap id="userAndOrderResultMap" type="com.topxin.pojo.User">
        <!-- 注意这里的user id属性值 -->
        <id property="id" column="uid" />
        <result property="username" column="username" />
        <result property="password" column="password" />
        <result property="birthday" column="birthday" />
        <collection property="ordersList" ofType="com.topxin.pojo.Orders">
            <result property="id" column="id" />
            <result property="ordertime" column="ordertime" />
            <result property="total" column="total" />
        </collection>
    </resultMap>
    <!-- 一对多查询 statement : namespacer.id = com.topxin.dao.UserMapper.findUserAndOrder-->
    <select id="findUserAndOrder" resultMap="userAndOrderResultMap">
        SELECT * FROM USER u LEFT JOIN orders o ON u.id = o.uid
    </select>
</mapper>

5.3 多对多

多个用户对应多个角色

-- 查询数据sql,对于多对多关系,都是通过中间表关联
SELECT * FROM USER u LEFT JOIN sys_user_role ur ON u.id = ur.userid
INNER JOIN sys_role r ON ur.roleid = r.id;
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.topxin.mapper.UserMapper">
    <resultMap id="userAndRoleResultMap" type="com.topxin.pojo.User">
        <id property="id" column="id" />
        <result property="username" column="username" />
        <result property="password" column="password" />
        <result property="birthday" column="birthday" />
        <collection property="roleList" ofType="com.topxin.pojo.Role">
            <result property="id" column="roleid" />
            <result property="roleName" column="roleName" />
            <result property="roleDesc" column="roleDesc" />
        </collection>
    </resultMap>
    <select id="findUserAndRole" resultMap="userAndRoleResultMap">
        SELECT * FROM USER u LEFT JOIN sys_user_role ur ON u.id = ur.userid INNER JOIN sys_role r ON ur.roleid = r.id;
    </select>
</mapper>

初始话用户角色关系表和角色表

-- 创建角色表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
​
-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `rolename` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `roleDesc` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
​
-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, 'CTO', 'CTO');
INSERT INTO `sys_role` VALUES (2, 'CEO', 'CEO');
​
SET FOREIGN_KEY_CHECKS = 1;
​
-- -------------------------------------------------
​
-- 创建用户角色中间表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
​
-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role`  (
  `userid` int(11) NOT NULL,
  `roleid` int(11) NOT NULL,
  PRIMARY KEY (`userid`, `roleid`) USING BTREE,
  INDEX `roleid`(`roleid`) USING BTREE,
  CONSTRAINT `sys_user_role_ibfk_1` FOREIGN KEY (`userid`) REFERENCES `sys_role` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `sys_user_role_ibfk_2` FOREIGN KEY (`roleid`) REFERENCES `user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
​
-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (1, 1);
INSERT INTO `sys_user_role` VALUES (2, 1);
INSERT INTO `sys_user_role` VALUES (1, 2);
INSERT INTO `sys_user_role` VALUES (2, 2);
​
SET FOREIGN_KEY_CHECKS = 1;

5.4 知识小结

MyBatis多表配置方式:

一对一配置:使用做配置

一对多配置:使用+做配置

多对多配置:使用+做配置

第六部分 Mybatis注解开发

注意:多表复杂操作更适合xml

6.1 常用注解

注解用途 
@Insert新增 
@Update更新 
@Delete删除 
@Select查询select
@Result封装结果集id和result
@Results与@Result配合,封装多个结果集resultMap
@One实现一对一结果集封装association
@Many实现一对多结果集封装collection

 

6.2 一对一

一个订单对应一个用户uid作为参数

// 用户查询
@Select(value = "select * from user where id = #{id}")
User selectById(Integer id);
//一对一查询
@Results({
@Result(property = "id", column = "id"),
@Result(property = "ordertime", column = "ordertime"),
@Result(property = "total", column = "total"),
@Result(
    javaType = User.class,
    property = "user",
    column = "uid",
    one = @One(select = "com.topxin.mapper.UserMapper.selectById"))
})
@Select(value = "select * from orders")
List<Orders> findAllOrdersAndUser();

6.3 一对多

一个用户对应多个订单

// select * from user u left join orders o on u.id = o.uid;
@Results({
    @Result(property = "id", column = "id"),
    @Result(property = "username", column = "username"),
    @Result(property = "password", column = "password"),
    @Result(property = "birthday", column = "birthday"),
    @Result(
        property = "orderList",
        column = "id",
        javaType = List.class,
        many = @Many(select = "com.topxin.mapper.OrdersMapper.selectByUId")
    )
})
@Select(value = "select * from user")
List<User> findAllUserAndOrder();
// 根据用户id返回查询结果
@Select(value = "select * from orders where uid = #{uid}")
List<Orders> selectByUId(Integer uid);

6.4 多对多

多个用户对应多个角色

//select * from user u left join sys_user_role ur on u.id = ur.userid left join sys_role r on ur.roleid = r.id;
@Results({
    @Result(property = "id", column = "id"),
    @Result(property = "username", column = "username"),
    @Result(property = "password", column = "password"),
    @Result(property = "birthday", column = "birthday"),
    @Result(
        property = "roleList",
        column = "id",
        javaType = List.class,
        many = @Many(select = "com.topxin.mapper.RoleMapper.selectByUserId")
    )
})
@Select(value = "select * from user")
List<User> findAllUserAndRole();
@Select(value = "select r.* from sys_role r inner join sys_user_role ur on r.id = ur.roleid where userid = #{uid}")
List<Role> selectByUserId(Integer uid);

6.5 注解动态sql

@Update({"<script>",
        "update Author",
        "  <set>",
        "    <if test='username != null'>username=#{username},</if>",
        "    <if test='password != null'>password=#{password},</if>",
        "  </set>",
        "where id=#{id}",
        "</script>"})
void updateUserValues(User user);

第七部分 Mybaits缓冲

mybatis使用了俩种缓冲,本地缓冲(local cache)和二级缓冲(second level cache)

 

7.1 一级缓冲

sqlSession(HashMap)

cachekey:statementId、params、boundSql、rowBounds组成

1)第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从 数据

库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。

2)如果中间sqlSession去执行commit操作(执行插入、更新、删除),则会清空SqlSession中的 一

级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。

3)第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直 接从

缓存中获取用户信息

4)手动清除一级缓冲sqlSession.claerCache();

@Test
public void localCacheTest(){
    // 使用sqlSession执行第一次查询
    List<User> u1 = userMapper.findUserAndOrder();
    // 使用sqlSession执行第二次查询
    List<User> u2 = userMapper.findUserAndOrder();
    System.out.println(u1 == u2);   //true
    sqlSession.close();
}

一级缓存原理探究与源码分析

一级缓存到底是什么?

一级缓存什么时候被创建?

一级缓存的工作流程是怎样的?

相信你现在应该会有这几个疑问,那么我们本节就来研究一下一级缓存的本质大家可以这样想,上面我们一直提到一级缓存,那么提到一级缓存就绕不开SqlSession,所以索性我们就直接从SqlSession,看看有没有创建缓存或者与缓存有关的属性或者方法

调研了一圈,发现上述所有方法中,好像只有clearCache()和缓存沾点关系,那么就直接从这个方法入手吧,分析源码时,我们要看它(此类)是谁,它的父类和子类分别又是谁,对如上关系了解了,你才会对这个类有更深的认识,分析了一圈,你可能会得到如下这个流程图

 

再深入分析,流程走到Perpetualcache中的clear()方法之后,会调用其cache.clear()方法,那么这个cache是什么东西呢?点进去发现,cache其实就是private Map cache = new HashMap();也就是一个Map,所以说cache.clear()其实就是map.clear(),也就是说,缓存其实就是本地存放的一个map对象,每一个SqISession都会存放一个map对象的引用,那么这个cache是何时创建的呢?

你觉得最有可能创建缓存的地方是哪里呢?我觉得是Executor,为什么这么认为?因为Executor是 执行器,用来执行SQL请求,而且清除缓存的方法也在Executor中执行,所以很可能缓存的创建也很 有可能在Executor中,看了一圈发现Executor中有一个createCacheKey方法,这个方法很像是创 建缓存的方法啊,跟进去看看,你发现createCacheKey方法是由BaseExecutor执行的,代码如下

CacheKey cacheKey = new CacheKey(); 
//MappedStatement 的 id 
// id就是Sql语句的所在位置包名+类名+ SQL名称 cacheKey.update(ms.getId()); 
// offset 就是 0 
cacheKey.update(rowBounds.getOffset()); 
// limit 就是 
Integer.MAXVALUE cacheKey.update(rowBounds.getLimit()); 
//具体的SQL语句 
cacheKey.update(boundSql.getSql()); 
//后面是update 了 sql中带的参数 
cacheKey.update(value); ... 
if (configuration.getEnvironment() != null) { 
// issue #176 cacheKey.update(configuration.getEnvironment().getId()); 

创建缓存key会经过一系列的update方法,udate方法由一个CacheKey这个对象来执行的,这个update方法最终由updateList的list来把五个值存进去,对照上面的代码和下面的图示,你应该能 理解这五个值都是什么了

 

这里需要注意一下最后一个值,configuration.getEnvironment().getId()这是什么,这其实就是 定义在mybatis-config.xml中的标签,见如下。

<environments default="development"> 
 <environment id="development"> 
  <transactionManager type="JDBC"/> 
   <dataSource type="POOLED"> 
    <property name="driver" value="${jdbc.driver}"/>          <property name="url" value="${jdbc.url}"/> 
    <property name="username" value="${jdbc.username}"/>        <property name="password" value="${jdbc.password}"/>      </dataSource> 
 </environment> 
</environments>

那么我们回归正题,那么创建完缓存之后该用在何处呢?总不会凭空创建一个缓存不使用吧?绝对不会的,经过我们对一级缓存的探究之后,我们发现一级缓存更多是用于查询操作,毕竟一级缓存也叫做查询缓存吧,为什么叫查询缓存我们一会儿说。我们先来看一下这个缓存到底用在哪了,我们跟踪到query方法如下:

//此方法在SimpleExecutor的父类BaseExecutor中实现
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //根据传入的参数动态获得SQL语句,最后返回用BoundSql对象表示
    BoundSql boundSql = ms.getBoundSql(parameter);
    //为本次查询创建缓存的Key
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    // 查询
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
​
 List<E> list;
 try {
     // queryStack + 1
     queryStack++;
     // 从一级缓存中,获取查询结果
     list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
     // 获取到,则进行处理
     if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
     // 获得不到,则从数据库中查询
     } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
     }
 } finally {
    queryStack--;
 }
 
 // 从数据库中读取操作
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    // 在缓存中,添加占位对象。此处的占位符,和延迟加载有关,可见 `DeferredLoad#canLoad()` 方法
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
        // 执行读操作
        list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
   } finally {
        // 从缓存中,移除占位对象
         localCache.removeObject(key);
   }
   // 添加到缓存中
   localCache.putObject(key, list);
   // 暂时忽略,存储过程相关
   if (ms.getStatementType() == StatementType.CALLABLE) {
        localOutputParameterCache.putObject(key, parameter);
    }
    return list;
}

如果查不到的话,就从数据库查,在queryFromDatabase中,会对localcache进行写入。 localcache对象的put方法最终交给Map进行存放

private Map<Object, Object> cache = new HashMap<Object, Object>(); 
@Override 
public void putObject(Object key, Object value) { 
    cache.put(key, value); 
}

7.2 二级缓存

 

开启二级缓冲

和一级缓存默认开启不一样,二级缓存需要我们手动开启

首先在全局配置文件sqlMapConfig.xml文件中加入如下代码:

<!--开启second level cache,local cache默认开启-->
<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>

其次在UserMapper.xml文件中开启缓存

<!--开启二级缓存--> 
<cache></cache>

我们可以看到mapper.xml文件中就这么一个空标签,其实这里可以配置,PerpetualCache这个类是mybatis默认实现缓存功能的类。我们不写type就使用mybatis默认的缓存,也可以去实现Cache接口来自定义缓存。

 

public class PerpetualCache implements Cache { 
    private final String id; 
    private MapcObject, Object> cache = new HashMapC);
    public PerpetualCache(St ring id) {this.id = id;}
}

我们可以看到二级缓存底层还是HashMap结构

public class User implements Serializable( 
    //用户ID private int id; 
    //用户姓名 private String username; 
    //用户性别 private String sex; 
}

开启了二级缓存后,还需要将要缓存的pojo实现Serializable接口,为了将缓存数据取出执行反序列化操 作,因为二级缓存数据存储介质多种多样,不一定只存在内存中,有可能存在硬盘中,如果我们要再取 这个缓存的话,就需要反序列化了。所以mybatis中的pojo都去实现Serializable接口

测试

测试二级缓存和sqlSession无关

@Test
public void secondLevelCacheTest(){
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    SqlSession sqlSession3 = sqlSessionFactory.openSession();
    UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
    UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
    UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);
    List<User> u1 = mapper1.findUserAndOrder();
    sqlSession1.close();
    List<User> u2 = mapper2.findUserAndOrder();
    System.out.println(u1 == u2);//false不是存储的地址
}

useCache和flushCache

mybatis中还可以配置userCache和flushCache等配置项,userCache是用来设置是否禁用二级缓 存的,在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出 sql去查询,默认情况是true,即该sql使用二级缓存

<select id="selectUserByUserId" useCache="false" resultType="com.lagou.pojo.User" parameterType="int"> 
    select * from user where id=#{id}
</select>

这种情况是针对每次查询都需要最新的数据sql,要设置成useCache=false,禁用二级缓存,直接从数 据库中获取。在mapper的同一个namespace中,如果有其它insert、update, delete操作数据后需要刷新缓 存,如果不执行刷新缓存会出现脏读。设置statement配置中的flushCache="true”属性,默认情况下为true,即刷新缓存,如果改成false则 不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。

<select id="selectUserByUserId" flushCache="true" useCache="false" resultType="com.lagou.pojo.User" parameterType="int"> 
    select * from user where id=#{id}
</select>

一般下执行完commit操作都需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏读。所以我们不用设置,默认即可

7.3 二级缓存整合redis

mybaits本身二级缓冲不支持分布式。

7.3.1 pom

<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-redis</artifactId>
    <version>1.0.0-beta2</version>
</dependency>

7.3.2 redis.properties

redis.host=localhost
redis.port=6379
redis.connectionTimeout=5000
redis.password=
redis.database=0

7.3.3 UserMapper.xml

<cache type="org.mybatis.caches.redis.RedisCache" />
​
<!-- 一对多查询 statement : namespacer.id 
com.topxin.dao.UserMapper.findUserAndOrder-->
<select id="findUserAndOrder" resultMap="userAndOrderResultMap" useCache="true">
    SELECT * FROM USER u LEFT JOIN orders o ON u.id = o.uid
</select>

7.3.4 Test

@Test
public void secondLevelCacheTest(){
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    SqlSession sqlSession3 = sqlSessionFactory.openSession();
​
    UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
    UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
    UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);
    sqlSession.close();     //关闭sqlSession1测试Redis缓冲是否成功
​
    List<User> u1 = mapper1.findUserAndOrder();
    sqlSession1.close();    //调用tmc中的commit()方法将map中的事务放入2级缓冲对象中
    List<User> u2 = mapper2.findUserAndOrder();
​
    System.out.println(u1 == u2);   //false不是存储的地址
}

7.3.5 源码分析

RedisCache和大家普遍实现Mybatis的缓存方案大同小异,无非是实现Cache接口,并使用jedis操作缓存;不过该项目在设计细节上有一些区别;

public final class RedisCache implements Cache {
    private final ReadWriteLock readWriteLock = new DummyReadWriteLock();
    private String id;
    private static JedisPool pool;
    public RedisCache(String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        } else {
            this.id = id;
            RedisConfig redisConfig = RedisConfigurationBuilder.getInstance().parseConfiguration();
            pool = new JedisPool(redisConfig, 
            redisConfig.getHost(), 
            redisConfig.getPort(),        
            redisConfig.getConnectionTimeout(), 
            redisConfig.getSoTimeout(), 
            redisConfig.getPassword(), 
            redisConfig.getDatabase(), 
            redisConfig.getClientName());
        }
    }
}

RedisCache在mybatis启动的时候,由MyBatis的CacheBuilder创建,创建的方式很简单,就是调用RedisCache的带有String参数的构造方法,即RedisCache(String id);而在RedisCache的构造方法中,调用了 RedisConfigu rationBuilder 来创建 RedisConfig 对象,并使用 RedisConfig 来创建JedisPool。

RedisConfig类继承了 JedisPoolConfig,并提供了 host,port等属性的包装,简单看一下RedisConfig的属性:

public class RedisConfig extends JedisPoolConfig {
    private String host = "localhost";
    private int port = 6379;
    private int connectionTimeout = 2000;
    private int soTimeout = 2000;
    private String password;
    private int database = 0;
    private String clientName;
}

RedisConfig对象是由RedisConfigurationBuilder创建的,简单看下这个类的主要方法:

 public RedisConfig parseConfiguration(ClassLoader classLoader) {
        Properties config = new Properties();
        InputStream input = classLoader.getResourceAsStream(this.redisPropertiesFilename);
        if (input != null) {
            try {
                config.load(input);
            } catch (IOException var12) {
                throw new RuntimeException("An error occurred while reading classpath property '" + this.redisPropertiesFilename + "', see nested exceptions", var12);
            } finally {
                try {
                    input.close();
                } catch (IOException var11) {
                }
            }
        }
        RedisConfig jedisConfig = new RedisConfig();
        this.setConfigProperties(config, jedisConfig);
        return jedisConfig;
    }

核心的方法就是parseConfiguration方法,该方法从classpath中读取一个redis.properties文件:

redis.host=localhost
redis.port=6379
redis.connectionTimeout=5000
redis.password=
redis.database=0

并将该配置文件中的内容设置到RedisConfig对象中,并返回;接下来,就是RedisCache使用RedisConfig类创建完成edisPool;在RedisCache中实现了一个简单的模板方法,用来操作Redis:

private Object execute(RedisCallback callback) {
        Jedis jedis = pool.getResource();
        Object var3;
        try {
            var3 = callback.doWithRedis(jedis);
        } finally {
            jedis.close();
        }
        return var3;
}

模板接口为RedisCallback,这个接口中就只需要实现了一个doWithRedis方法而已:

public interface RedisCallback {
    Object doWithRedis(Jedis jedis); 
}

接下来看看Cache中最重要的两个方法:putObject和getObject,通过这两个方法来查看mybatis-redis储存数据的格式

public void putObject(final Object key, final Object value) {
        this.execute(new RedisCallback() {
            public Object doWithRedis(Jedis jedis) {
                jedis.hset(RedisCache.this.id.toString().getBytes(), key.toString().getBytes(), SerializeUtil.serialize(value));
                return null;
            }
        });
    }
​
public Object getObject(final Object key) {
        return this.execute(new RedisCallback() {
            public Object doWithRedis(Jedis jedis) {
                return SerializeUtil.unserialize(jedis.hget(RedisCache.this.id.toString().getBytes(), key.toString().getBytes()));
            }
        });
    }

可以很清楚的看到,mybatis-redis在存储数据的时候,是使用的hash结构,把cache的id作为这个hash 的key (cache的id在mybatis中就是mapper的namespace);这个mapper中的查询缓存数据作为hash的field,需要缓存的内容直接使用SerializeUtil存储,SerializeUtil和其他的序列化类差不多,负责对象的序列化和反序列化;

第八部分 Mybatis插件

8.1 插件原理

一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展。这样的好处是显而易见的,一是增加了框架的灵活性。二是开发者可以结合实际需求,对框架进行拓展,使其能够更好的工作。以MyBatis为例,我们可基于MyBati s插件机制实现分页、分表,监控等功能。由于插件和业务 无关,业务也无法感知插件的存在。因此可以无感植入插件,在无形中增强功能

8.2 Mybatis插件介绍

Mybati s作为一个应用广泛的优秀的ORM开源框架,这个框架具有强大的灵活性,在四大组件(Executor、StatementHandler、ParameterHandler、ResultSetHandler)处提供了简单易用的插 件扩展机制。Mybatis对持久层的操作就是借助于四大核心对象。MyBatis支持用插件对四大核心对象进 行拦截,对mybatis来说插件就是拦截器,用来增强核心对象的功能,增强功能本质上是借助于底层的 动态代理实现的,换句话说,MyBatis中的四大对象都是代理对象

 

MyBatis所允许拦截的方法如下

  • 执行器Executor (update、query、commit、rollback等方法);

  • SQL语法构建器StatementHandler (prepare、parameterize、batch、updates query等方 法);

  • 参数处理器ParameterHandler (getParameterObject、setParameters方法);

  • 结果集处理器ResultSetHandler (handleResultSets、handleOutputParameters等方法);

8.3 插件原理

在四大对象创建的时候

  • 每个创建出来的对象不是直接返回的,而是interceptorChain.pluginAll(parameterHandler);

  • 获取到所有的Interceptor (拦截器)(插件需要实现的接口);调用 interceptor.plugin(target);返

    回 target 包装后的对象

  • 插件机制,我们可以使用插件为目标对象创建一个代理对象;AOP (面向切面)我们的插件可 以

    为四大对象创建出代理对象,代理对象就可以拦截到四大对象的每一个执行;

拦截

插件具体是如何拦截并附加额外的功能的呢?以ParameterHandler来说

8.3 自定义插件

8.4 插件源码分析

8.5 pageHelper分页插件

8.5.1 坐标

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>3.7.5</version>
</dependency>
<dependency>
    <groupId>com.github.jsqlparser</groupId>
    <artifactId>jsqlparser</artifactId>
    <version>0.9.1</version>
</dependency>

配置

8.6 通用mapper

坐标

配置

映射

第九部分 架构原理

9.1 架构设计

 

9.3 工作流程

 

第十部门 源码剖析

10.1 传统方式源码剖析

10.1.1 源码剖析初始化

Test类方法读取配置文件,加载文件流

//1.读取字节文件,转换蔚输入流,注意现在还没有解析
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
​
//2.解析配置文件,封装Configuration对象,创建DefaultSqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

进入SqlSessionFactoryBuilder类调用build()的重载方法

// 2.调用的重载方法
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        // 创建 XMLConfigBuilder, XMLConfigBuilder是专门解析mybatis的配置文件的类
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        // 执行 XML 解析
        // 创建 DefaultSqlSessionFactory 对象
        return build(parser.parse());
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
        ErrorContext.instance().reset();
        try {
            inputStream.close();
        } catch (IOException e) {
            // Intentionally ignore. Prefer previous error.
        }
    }
}

进入XMLConfigBuilder的parse()方法解析xml并封装对象Configuration,Configuration对象属性与Xml配置是对应的

注意:在Myabtis初始化中会将Myabatis配置文件中的信息全部加载到内存中,使用

org.apache.ibatis.session.Configuration 实例来维护

/**
 * 解析 XML 成 Configuration 对象。
 *
 * @return Configuration 对象
 */
public Configuration parse() {
    // 若已解析,抛出 BuilderException 异常
    if (parsed) {
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    // 标记已解析
    parsed = true;
    ///parser是XPathParser解析器对象,读取节点内数据,<configuration>是MyBatis配置文件中的顶层标签
    // 解析 XML configuration 节点
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}
​
 /**
     * 解析 XML
     *
     * 具体 MyBatis 有哪些 XML 标签,参见 《XML 映射配置文件》http://www.mybatis.org/mybatis-3/zh/configuration.html
     *
     * @param root 根节点
     */
    private void parseConfiguration(XNode root) {
        try {
            //issue #117 read properties first
            // 解析 <properties /> 标签
            propertiesElement(root.evalNode("properties"));
            // 解析 <settings /> 标签
            Properties settings = settingsAsProperties(root.evalNode("settings"));
            // 加载自定义的 VFS 实现类
            loadCustomVfs(settings);
            // 解析 <typeAliases /> 标签
            typeAliasesElement(root.evalNode("typeAliases"));
            // 解析 <plugins /> 标签
            pluginElement(root.evalNode("plugins"));
            // 解析 <objectFactory /> 标签
            objectFactoryElement(root.evalNode("objectFactory"));
            // 解析 <objectWrapperFactory /> 标签
            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            // 解析 <reflectorFactory /> 标签
            reflectorFactoryElement(root.evalNode("reflectorFactory"));
            // 赋值 <settings /> 到 Configuration 属性
            settingsElement(settings);
            // read it after objectFactory and objectWrapperFactory issue #631
            // 解析 <environments /> 标签
            environmentsElement(root.evalNode("environments"));
            // 解析 <databaseIdProvider /> 标签
            databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            // 解析 <typeHandlers /> 标签
            typeHandlerElement(root.evalNode("typeHandlers"));
            // 解析 <mappers /> 标签
            mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }

MappedStatement对象

与Mapper配置文件中的一个select/update/insert/delete节点相对应。

初始化Myabtis将MappedStatement对象封装在Configuration对象属性Map中

存储的key = 全限类名 + 方法名,通过key来找到对应的数据

/**
 * MappedStatement 映射
 *
 * KEY:`${namespace}.${id}`
 */
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");

Test方法步骤2中调用build()重载方法

/**
 * 创建 DefaultSqlSessionFactory 对象
 *
 * @param config Configuration 对象
 * @return DefaultSqlSessionFactory 对象
 */
public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config); //构建者设计模式
}

10.1.2 源码剖析执行SQL流程

SqlSession它有两个实现类:DefaultSqlSession (默认)和SqlSessionManager (弃用,不做介绍)

SqlSession是Myabatis中用于和数据库交互的顶层类,通常将它和ThreadLocal绑定,一个会话使用一个sqlSeesion,使用完毕后调用close()方法关闭资源

public class DefaultSqlSession implements SqlSession { 
    private final Configuration configuration;  //配置信息封装的对象
    private final Executor executor;    //执行器
}

10.1.3 源码剖析executor

10.1.4 源码剖析StatementHandler

10.2 Mapper代理方式

10.2.1 源码剖析getmapper()

10.2.2 源码剖析invoke()

10.3 二级缓冲源码剖析

10.3.1 启动二级缓冲

开启全局二级缓冲配置

<settings> 
    <setting name="cacheEnabled" value="true"/> 
</settings>

在需要二级缓冲的Mapper中配置标签

<cache></cache>

在具体的CURD标签上配置useCache=true

<select id="findById" resultType="com.lagou.pojo.User" useCache="true"> 
    select * from user where id = #{id} 
</select>

10.3.2 标签cache解析

(1)根据之前的mybatis源码剖析,xml的解析工作主要交给XMLConfigBuilder.parse()方法来实现

(2)先来看看是如何构建Cache对象的MapperBuilderAssistant.useNewCache()

我们看到一个Mapper.xml只会解析一次标签,也就是只创建一次Cache对象,放进configuration中,

(3)并将cache赋值给MapperBuilderAssistant.currentCache()

buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

将Cache包装到MappedStatement

10.3.3 总结

在二级缓冲设计上,maybatis大量运用了装饰者模式,入CachingExecutor,以及各种Cache的装饰器。

二级缓冲实现了sqlsession之间的缓冲数据共享,属于namespace级别

二级缓冲具有丰富的缓冲策略

二级缓冲由多个装饰器,与基础缓冲组合而成

二级缓冲工作由一个缓冲装饰器CacheingExexutor和一个事务预警缓冲TransactionlCache完成

如果开启二级缓冲

查询方法先查看2级别缓冲中是否由数据,然后去以及缓冲中查找,都没有的话查询数据库

二级缓冲在调用tcm事务类中的commit方法才会生效,最初二级缓冲存放在一个map中,没有这个事务管理会产生桩读。

10.4 延迟加载源码剖析

10.4.1 什么是延迟加载

就是在需要用到的数据才进行加载,不需要用到数据的时候就不加载,延迟加载就是懒加载

注意:延迟加载是基于嵌套查询来实现的

一对多,多对多可以采用延迟加载

一对一(多对一)建议使用立即加载

Mybatis默认使用立即加载

10.4.2 局部延迟加载

<!--在association和collection标签中都有一个fetchType属性-->
fetchType=“lazy”    //延迟加载  //eager立即加载

10.4.3 全局延迟加载

<settings> 
    <!--开启全局延迟加载功能--> 
    <setting name="lazyLoadingEnabled" value="true"/> 
</settings>

10.4.4 延迟加载原理实现

它的原理是,使用 CGLIB 或 Javassist( 默认 ) 创建目标对象的代理对象。

当调用代理对象的延迟加载属性的 getting 方法时,进入拦截器方法invoke()。

总结:延迟加载主要是通过动态代理的形式实现,通过代理拦截到指定方法,执行数据加载。

10.4.5 延迟加载invoke()

判断是否延迟包含延迟加载属性

10.5 设计模式

10.5.1 Builder构建着模型

相对于工厂模型会产出一个完整的产品;Builder应用于更加复杂的对象构建,甚至只会构建产品的一部分

使用多个简单对象一步一步构建一个发杂的对象

创建一个简单对象

package com.topxin.constructor;
​
public class Computer {
    private String disPlayer;
    private String mainUnit;
    private String mouse;
    private String keyboard;
    ...get/set/toString
}

创建ComputerBuilder复杂类和getComputer()方法

package com.topxin.constructor;
​
public class ComputerBuilder {
​
    private Computer computer = new Computer();
​
    public void installDisPlayer(String disPlayer){
        computer.setDisPlayer(disPlayer);
    }
​
    public void installMainUnit(String mainUnit){
        computer.setMainUnit(mainUnit);
    }
​
    public void installKeyboard(String keyboard){
        computer.setKeyboard(keyboard);
    }
​
    public void installMouse(String mouse){
        computer.setMouse(mouse);
    }
​
    public Computer getComputer(){
        return computer;
    }
}

测试

package com.topxin.constructor;
​
public class ConstructorTest {
    public static void main(String[]args){
        ComputerBuilder computerBuilder = new ComputerBuilder();
        computerBuilder.installDisPlayer("显万器");
        computerBuilder.installMainUnit("主机");
        computerBuilder.installKeyboard("键盘");
        computerBuilder.installMouse("鼠标");
        Computer computer = computerBuilder.getComputer();
        System.out.println(computer);
    }
}

10.5.2 工厂模式

简单工厂类:可以根据参数的不同返回不同类的实例

构架抽象类

package com.topxin.simpleFactory;
​
public abstract class Computer {
    public abstract void start();
}

新建惠普类

package com.topxin.simpleFactory;
​
public class HpComputer extends  Computer {
    @Override
    public void start() {
        System.out.println("hp");
    }
}

理想类

package com.topxin.simpleFactory;
​
public class LenovoComputer extends Computer {
    @Override
    public void start() {
        System.out.println("lenovo");
    }
}

工厂类

package com.topxin.simpleFactory;
​
public class ComputerFactory {
    public static Computer createComputer(String type){
        Computer computer = null;
        switch (type){
            case "lenovo":
                computer = new LenovoComputer();
            case "hp":
                computer = new HpComputer();
        }
        return  computer;
    }
}
​

测试

通过工厂类createComputer可以创建不同类型的类

package com.topxin.simpleFactory;
​
public class SimpleFactoryTest {
    public static void main(String[] args) {
        Computer hp = ComputerFactory.createComputer("hp");
        hp.start();
    }
}

10.5.3 代理模式

Mybatis核心使用

接口

package com.topxin.dynamicprocy;
​
public interface Person {
    void doSomething();
}
​

具体实现类

package com.topxin.dynamicprocy;
​
public class Bob implements Person{
    @Override
    public void doSomething() {
        System.out.println("Bob doSomeThing");
    }
}

根据JDC动态代理

package com.topxin.dynamicprocy;
​
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
​
public class JDKDynamicProxy implements InvocationHandler {
​
    //声明被代理的对象
    private Person person;
​
    public JDKDynamicProxy(Person person) {
        this.person = person;
    }
​
    //获取代理对象
    public Object getTarget(){
        Object proxyInstance = Proxy.newProxyInstance(person.getClass().getClassLoader(), person.getClass().getInterfaces(), this);
        return proxyInstance;
    }
​
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("前置增强");
        Object invoke = method.invoke(person, args);
        System.out.println("后置增强");
        return invoke;
    }
}

测试

获取目标最终都会调用invoke()方法

package com.topxin.dynamicprocy;
​
public class ProxyTest {
    public static void main(String[] args) {
        Person person = new Bob();
        person.doSomething();
        Person proxy = (Person) new JDKDynamicProxy(new Bob()).getTarget();
        proxy.doSomething();
    }
}
​

10.6 异常日志输出

使用ThreadLocal来管理ErrorContext

10.7 动态sql源码剖析

第十一部分 Mybatis-Plus

11.1 什么是Mybatis-Plus

MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简

化开发、提高效率而生。

注意@TableName指定数据库表个实体类映射关系

11.2 mybatis+mp

11.3 mybatis+mp+spring

11.4 mybatis+mp+springboot

11.5

@TableName()

@TableName("数据库表名称")    //指定表映射不同

@TableId()

@TableId(type = IdType.AUTO) //指定id类型为自增长等6中策略

@TableFiled()字段

@TableFiled(select = false)     //不反悔,不查询改字段
@TableFiled(value = "数据库对应的字段") //字段映射名称不同
@TableFiled(exist = false)      //数据库中不存在

分页拦截器

package com.topxin; 
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; 
import org.mybatis.spring.annotation.MapperScan; 
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.Configuration;
@Configuration 
@MapperScan("com.lagou.mp.mapper") //设置mapper接口的扫描包 
public class MybatisPlusConfig { 
    /*** 分页插件 */
    @Bean 
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor(); 
    } 
}

第十二部分 Mybatis简答题

12.1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值