mybatis学习相关总结

1.最原始的操作数据库
Class.forName("com.mysql.jdbc.Driver");
  // 打开连接
  conn = DriverManager.getConnection(DB_URL, USER, PASSWORD);
  // 执行查询
  stmt = conn.createStatement();
  String sql= "SELECT bid, name, author_id FROM blog";
  ResultSet rs = stmt.executeQuery(sql);
  // 获取结果集
  while(rs.next()){
      int bid  = rs.getInt("bid");
      String name = rs.getString("name");
      String authorId = rs.getString("author_id");
  }
2.ORM要解决哪些问题
    (1).每次查询创建连接的问题(可以配置连接池)
    (2).行数据和实体映射的问题
    (3).sql语句和业务逻辑分离
    (4).查询缓存的功能

3.mybatis不会自动生成全部的SQL 语句,主要解决的是 SQL 和对象的映射问题。
    使用连接池对连接进行管理
    SQL 和代码分离,集中管理
    结果集映射
    参数映射和动态 SQL
    重复 SQL 的提取
    缓存管理
    插件机制
4.编程方式mybatis应用
public void testMapper() throws IOException {
      //读取mybatis主配置文件
      String resource = "mybatis-config.xml";
      InputStream inputStream = Resources.getResourceAsStream(resource);
      //构建SqlSessionFactory
      SqlSessionFactory sqlSessionFactory = new
      SqlSessionFactoryBuilder().build(inputStream);
      //获取一个session
      SqlSession session = sqlSessionFactory.openSession();
      try {
          //获取代理之后的mapper对象
          BlogMapper mapper = session.getMapper(BlogMapper.class);
          Blog blog = mapper.selectBlogById(1);
          System.out.println(blog);
      } finally {
          session.close();
      }
 }
5.为何不用编程方式的mybatis
  对象的生命周期怎么管理,何时去销毁对象

6.mybatis中重要组件的生命周期
  SqlSessionFactoryBuilder
  用来构建 SqlSessionFactory 的,而 SqlSessionFactory 只需要一个,所以只要构建了这一个 SqlSessionFactory,它的使命 就完成了,也就没有存在的意义了。所以它的生命周期只存在于方法的局部。
  SqlSessionFactory(采用单例模式)
  SqlSessionFactory 是用来创建 SqlSession 的,每次应用程序访问数据库,都需要 创建一个会话。因为我们一直有创建会话的需要,所以 SqlSessionFactory 应该存在于 应用的整个生命周期中(作用域是应用作用域)
  SqlSession
  SqlSession 是一个会话,因为它不是线程安全的,不能在线程间共享。所以我们在 请求开始的时候创建一个 SqlSession 对象,在请求结束或者说方法执行完毕的时候要及 时关闭它(一次请求或者操作中)
  Mapper
  方法内

7.MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括

  Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  ParameterHandler (getParameterObject, setParameters)
  ResultSetHandler (handleResultSets, handleOutputParameters)
  StatementHandler (prepare, parameterize, batch, update, query)
  mybatis中的Invocation
  目标对象Object
  方法Method
  参数数组
  proceed()执行目标对象的方法
  Plugin类实现了InvocationHandler
  wrap()方法生成一个目标对象的代理对象Proxy.newProxyInstance

8.如何实现一个自定义的类型转换

 xxxTypeHandler extends BaseTypeHandler<T>{
    setNonNullParameter() java类型到jdbc类型
    getNullableResult() 获取空结果集(根据列名)
    getNullableResult() 获取空结果集(根据下标值)
    getNullableResult:存储过程用的
  }

  第二步,在 mybatis-config.xml 文件中注册
  <typeHandlers>
    <typeHandler handler="类路径"></typeHandler>
  </typeHandlers>

  使用
  插入值的时候,从 Java 类型到 JDBC 类型,在字段属性中指定 typehandler
  #{name,jdbcType=VARCHAR,typeHandler=xxxTypeHandler}
  返回值的时候,从 JDBC 类型到 Java 类型,在 resultMap 的列上指定 typehandler
             <result
                  column="name"
                  property="name"
                  jdbcType="VARCHAR"
                  typeHandler="xxxTypeHandler"/>
9.创建对象的实例ObjectFactory
 
 //最终创建实例的方法
  private  <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
      try {
        Constructor<T> constructor;
        //如果构造器没有参数
        if (constructorArgTypes == null || constructorArgs == null) {
          //获取默认构造器
          constructor = type.getDeclaredConstructor();
          try {
            return constructor.newInstance();
          } catch (IllegalAccessException e) {
            if (Reflector.canControlMemberAccessible()) {
              constructor.setAccessible(true);
              return constructor.newInstance();
            } else {
              throw e;
            }
          }
        }
        //获取带参数的构造器
        constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[0]));
        try {
          return constructor.newInstance(constructorArgs.toArray(new Object[0]));
        } catch (IllegalAccessException e) {
          if (Reflector.canControlMemberAccessible()) {
            constructor.setAccessible(true);
            return constructor.newInstance(constructorArgs.toArray(new Object[0]));
          } else {
            throw e;
          }
        }
      } catch (Exception e) {
        String argTypes = Optional.ofNullable(constructorArgTypes).orElseGet(Collections::emptyList)
            .stream().map(Class::getSimpleName).collect(Collectors.joining(","));
        String argValues = Optional.ofNullable(constructorArgs).orElseGet(Collections::emptyList)
            .stream().map(String::valueOf).collect(Collectors.joining(","));
        throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e);
      }
    }
10.什么时候调用了 objectFactory.create()?
   创建 DefaultResultSetHandler 的时候,和创建对象的时候
   创建对象后,已有的属性为什么被覆盖了?
   在 DefaultResultSetHandler 类的getRowValue()方法里面里面调用了 applyPropertyMappings()。
   返回结果的时候,ObjectFactory 和 TypeHandler 哪个先工作?
   先是 ObjectFactory,再是 TypeHandler。肯定是先创建对象。

11.批量插入
  
 <insert id="batchInsert" parameterType="java.util.List" userGenerateKeys="true">
        <selectKey resultType="long" keyProperty="id" order="AFTER">
            select LAST_INSERT_ID()
        </selectKey>
        insert into tbl_emp(emp_id,name) values
        <foreach collection="list" item="emps" index="index" separator=",">
            (#{emps.empId},#{emps.name})
        </foreach>
   </insert>
   效率要比循环发送 SQL 执行要高得多。最关键的地方就在于减少 了跟数据库交互的次数,并且避免了开启和结束事务的时间消耗

12.批量更新
  
 update tbl_emp set
   emp_name =
   case emp_id
           gender =
                  case emp_id
                          when ? then ?
                          when ? then ?
                          when ? then ? end ,
           email =
                  case emp_id
                          when ? then ?
                          when ? then ?
                          when ? then ? end
   where emp_id in ( ? , ? , ? )
13.异常问题的解决
   Caused by: com.mysql.jdbc.PacketTooBigException:
   Packet for query is too large (7188967 > 4194304).
   You can change this value on the server by setting the max_allowed_packet' variable
   MySQL 的服务端对于接收的数据包有大小限制,max_allowed_packet 默认是 4M,需要修改默认配置才可以解决这个问题

14.映射结果的标签
   resultType 是 select 标签的一个属性,适用于返回 JDK 类型(比如 Integer、String 等等)和实体类
   如果返回的字段无法直接映射,就要用 resultMap 来建立映射关系

15.SqlSession里面有executor,executor里面有具体的执行sql的逻辑,获取连接,获取statement对象,执行sql,处理结果

16.SimpleExecutor:每执行一次 update 或 select,就开启一个 Statement 对象,用 完立刻关闭 Statement 对象。

17.StatementHandler 里面包含了处理参数的 ParameterHandler 和处理结果集的 ResultSetHandler。
   用 new 出来的 StatementHandler 创建 Statement 对象——prepareStatement() 方法对语句进行预编译,处理参数。
   handler.parameterize(stmt) ;
   执行 PreparedStatement 的 execute()方法
   resultSetHandler.handleResultSets(ps);

18.MapperAnnotationBuilder.parseStatement()时候会调用
   //添加mapper与语句的映射关系
   MapperBuilderAssistant.addMappedStatement();
   存储到主配置中的map中mapper全路径对应的statement


19.如果没有orm框架,如何将数据库返回映射到实体类
   利用jdbcTemplate的RowMapper接口
  
 public class EmployeeRowMapper implements RowMapper{

        //重写mapRow方法
        public Object mapRow(ResultSet rs,int i){
            Employee emp = new Employee();
            employee.setEmpId(resultSet.getInt("emp_id"));
            employee.setEmpName(resultSet.getString("emp_name"));
            employee.setEmail(resultSet.getString("emial"));
            return emp;
        }

   }

   dao层调用:
   public List<Employee> query(String sql){
        new JdbcTemplate( new DruidDataSource());
        return jdbcTemplate.query(sql,new EmployeeRowMapper());
   }
   如何解决实体与数据库字段的映射:
   名称如何对应
   数据类型如何对应


//负责 java -> db类型  db -> java类型的转换
public interface TypeHandler<T> {

  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  /**
   * @param columnName Colunm name, when configuration <code>useColumnLabel</code> is <code>false</code>
   */
  T getResult(ResultSet rs, String columnName) throws SQLException;

  T getResult(ResultSet rs, int columnIndex) throws SQLException;

  T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

TypeHandler需要注册到TypeHandlerRegistry


ObjectFactory用来创建新对象
通过反射来创建对象

自定义工厂对象,来实现修改对象工厂在初始化实体类的时候的行为
继承DefaultObjectFactory,重写create方法
config里面注册:

接口和statement绑定是通过全限定类名
如何拿到sql:
MappedStatement ms = configuration.getMappedStatement(statement);


settings 里面提供了一个 ProxyFactory 属性。
MyBatis 默认使用 JAVASSIST 创建代理对象。也可以改为 CGLIB,这时需要引入 CGLIB 的包

通用 Mapper 还可以解决:
1、 每个 Mapper 接口中大量的重复方法的定义;
2、 屏蔽数据库的差异;
3、 提供批量操作的方法;
4、 实现分页。

缓存时放在executor中维护
在同一个会话里面,多次执行相同的 SQL 语句,会直接从内存取到缓存的结果,不会再发送 SQL 到数据库
一级缓存在 BaseExecutor 的 query()——queryFromDatabase()中存入。在 queryFromDatabase()之前会 get()。
同一个会话中,update(包括 delete)会导致一级缓存被清空
一级缓存是在 BaseExecutor 中的 update()方法中调用 clearLocalCache()清空的 (无条件),query 中会判断。

一级缓存的不足:
使用一级缓存的时候,因为缓存不能跨会话共享,不同的会话之间对于相同的数据 可能有不一样的缓存。在有多个会话或者分布式环境下,会存在脏数据的问题

二级缓存:
二级缓存是用来解决一级缓存不能跨会话共享的问题的,范围是 namespace 级别 的,
可以被多个 SqlSession 共享(只要是同一个接口里面的相同方法,都可以共享), 生命周期和应用同步。

二级缓存在sqlSession外层,一级缓存在sqlSession内层。也就是只有取不到二级缓存的情况下才到一个会话中去取一级缓存

实际上 MyBatis 用了一个装饰器的类来维护,就是 CachingExecutor。
如果启用了 二级缓存,MyBatis 在创建 Executor 对象的时候会对 Executor 进行装饰。

事务不提交,二级缓存不存在

在什么情况下才有必要去开启二级缓存?
所以适合在查询为主 的应用中使用,比如历史交易、历史订单的查询。否则缓存就失去了意义

推荐在一个 Mapper 里面只操作单表的情况使用?
二级缓存带来的脏数据问题

跨 namespace 的缓存共享的问题,可以使用<cache-ref>来解决


使用第三方缓存:
Mapper.xml 配置,type 使用 RedisCache

Mybatis插件原理:

//
public class Invocation {

  private final Object target;
  private final Method method;
  private final Object[] args;

  public Invocation(Object target, Method method, Object[] args) {
    this.target = target;
    this.method = method;
    this.args = args;
  }

  public Object getTarget() {
    return target;
  }

  public Method getMethod() {
    return method;
  }

  public Object[] getArgs() {
    return args;
  }

  public Object proceed() throws InvocationTargetException, IllegalAccessException {
    return method.invoke(target, args);
  }

}


//拦截器链
public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<>();

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }

  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}

可以拦截的对象:
Executor:上层的对象,SQL执行 全过程,包括组装参 数,组装结果集返回和 执行 SQL 过程
update方法:执行 update、insert、delete 操作

StatementHandler:执行 SQL 的过程,最常 用的拦截对象
prepare:预编译,parameterize设置参数,batch批处理,update增删改,

ParameterHandler:参数组装

ResultSetHandler:处理结果

Executor 会拦截到 CachingExcecutor 或者 BaseExecutor

代理执行顺序,从外层到内层

创建代理对象Plugin.wrap(object,Interceptor)

public static Object wrap(Object target, Interceptor interceptor) {
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }


 @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set<Method> 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) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    // issue #251
    if (interceptsAnnotation == null) {
      throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
    }
    Signature[] sigs = interceptsAnnotation.value();
    Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
    for (Signature sig : sigs) {
      Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
      try {
        Method method = sig.type().getMethod(sig.method(), sig.args());
        methods.add(method);
      } catch (NoSuchMethodException e) {
        throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
      }
    }
    return signatureMap;
  }

  private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
    Set<Class<?>> interfaces = new HashSet<>();
    while (type != null) {
      for (Class<?> c : type.getInterfaces()) {
        if (signatureMap.containsKey(c)) {
          interfaces.add(c);
        }
      }
      type = type.getSuperclass();
    }
    return interfaces.toArray(new Class<?>[interfaces.size()]);
  }

PageHelper.startPage()主要是把分页变量设置到ThreadLocal中

可以使用拦截器进行分表操作,数据加解密操作

数据权限的控制


spring整合mybatis中的配置文件是为了做什么
创建mybatis-spring的SqlSessionFactoryBean 实现了 InitializingBean 接口,所以要实现 afterPropertiesSet()方法,
这个方法 会在 bean 的属性值设置完的时候被调用。

另外它实现了 FactoryBean 接口,所以它初始化的时候,实际上是调用 getObject() 方法,
它里面调用的也是 afterPropertiesSet()方法。
this.sqlSessionFactory = buildSqlSessionFactory();

SqlSessionFactoryBean是单例

某个对象实现FactoryBean接口,可以通过重写getObject方法在创建对象的时候做一些额外的操作。
例如mybatis的SqlSessionFactoryBean在getObject时候,如果对象为空,则对调用afterPropertiesSet去初始化.

BeanDefinitionRegistryPostProcessor 注入 BeanDefination 时添加操作


但是在 Spring 里面,我们不是直接使用 DefaultSqlSession 的,而是对它进行了一个封装,
这个 SqlSession 的实现类就是 SqlSessionTemplate。这个跟 Spring 封装其他的组件是一样的

SqlSessionTemplate单例,为什么是线程安全的

那么 接下来看一下怎么在 DAO 层拿到一个 SqlSessionTemplate?
DAO 层的实现类继承 SqlSessionDaoSupport,就可以获得 SqlSessionTemplate,然后在里面封装 SqlSessionTemplate 的方法

我们只是注入了一个 接口,在对象实例化的时候,是怎么拿到 SqlSessionTemplate 的?当我们调用方法的时 候,还是不是用的 MapperProxy?
MapperScannerConfigurer 实现了 BeanDefinitionRegistryPostProcessor 接口,
BeanDefinitionRegistryPostProcessor 是 BeanFactoryPostProcessor 的子类,可以
通过编码的方式修改、新增或者删除某些 Bean 的定义。

mapper在注册 beanDefinitions 的时候,BeanClass 被改为 MapperFactoryBean
MapperFactoryBean 继 承 了 SqlSessionDaoSupport , 可 以 拿 到SqlSessionTemplate。

Spring 会根据 Mapper 的名字从 BeanFactory 中获取它的 BeanDefination,再从 BeanDefination 中 获 取 BeanClass ,
EmployeeMapper 对 应 的 BeanClass 是 MapperFactoryBean(上一步已经分析过)。
接下来就是创建 MapperFactoryBean,因为实现了 FactoryBean 接口,同样是调 用 getObject()方法。

BeanFactory是用来获取bean的,
FactoryBean是用来创建bean的

所以最后调用 Mapper 接口的方法,也是执行 MapperProxy 的 invoke()方法

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值