Mybatis

作者: 攻城狮0902 | 来源:发表于2018-09-15 21:43 被阅读0次

    Mybatis

    概念

    • SqlSession
      can execute commands, get mappers and manage transactions, Closing the session is very important
    • SqlSessionFactory instance
      can create instances of SqlSessions all different ways, should exist for the duration of your application execution
    • SqlSessionFactoryBuilder
      can create the SqlSessonFactory from XML, Annotations or hand coded Java configuration
    • MapperFactoryBean
    String resource = "org/mybatis/builder/mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    SqlSessionFactory factory = builder.build(inputStream);
    
    SqlSession session = sqlSessionFactory.openSession();
    try {
      BlogMapper mapper = session.getMapper(BlogMapper.class);
      // do work
    } finally {
      session.close();
    }
    
    1. Transaction use a transaction scope for the session, or use auto-commit
    2. Connection
    3. Execution reuse PreparedStatements and/or batch updates

    全局配置

    <settings>
      <setting name="cacheEnabled" value="true"/>
      <setting name="lazyLoadingEnabled" value="true"/>
      <setting name="multipleResultSetsEnabled" value="true"/>
      <setting name="useColumnLabel" value="true"/>
      <setting name="useGeneratedKeys" value="false"/>
      <setting name="autoMappingBehavior" value="PARTIAL"/>
      <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
      <setting name="defaultExecutorType" value="SIMPLE"/>
      <setting name="defaultStatementTimeout" value="25"/>
      <setting name="defaultFetchSize" value="100"/>
      <setting name="safeRowBoundsEnabled" value="false"/>
      <setting name="mapUnderscoreToCamelCase" value="false"/>
      <setting name="localCacheScope" value="SESSION"/>
      <setting name="jdbcTypeForNull" value="OTHER"/>
      <setting name="lazyLoadTriggerMethods"
        value="equals,clone,hashCode,toString"/>
    </settings>
    

    typeAliases

    • 可以挨个指定或者typeAliasesPackage指定目录(没使用@Alias("author")指定bean名的话,就使用bean类名作为别名)
    • built-in type aliases for common Java types 见详细文档
      别名和java基本类型对应关系:
      91fabf5f.png

    基本类型和数据库类型的对应关系:


    97f21b4a.png

    plugins

    Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
    ParameterHandler (getParameterObject, setParameters)
    ResultSetHandler (handleResultSets, handleOutputParameters)
    StatementHandler (prepare, parameterize, batch, update, query)

    transactionManager

    • JDBC
    • MANAGED does almost nothing

    dataSource

    • UNPOOLED
    • POOLED
    • JNDI intended for use with containers such as EJB or Application Servers

    元素

    <!--  be used to define a reusable fragment of SQL code -->
    <sql id="queryCondition">
    </sql>
    
    <insert id="insertPostUpload" parameterType="ForumAppModel" >
    <!-- 获取id -->
            <selectKey resultType="java.lang.String"  <!-- STATEMENT, PREPARED or CALLABLE -->
          order="BEFORE"   <!-- BEFORE or AFTER -->
                keyProperty="id"  <!-- STATEMENT, PREPARED or CALLABLE -->
          >
                SELECT seq_user_forum_app.nextval AS ID FROM
                DUAL
            </selectKey>
    </insert>
    
    
    <select
      id="selectPerson"
      parameterType="int" 
      resultType="hashmap"
      flushCache="false"  <!-- cause the local and 2nd level caches to be flushed whenever this statement is called -->
      useCache="true"   <!-- cause the results of this statement to be cached in 2nd level cache -->
      timeout="10000"
      fetchSize="256"
      statementType="PREPARED"  <!-- STATEMENT, PREPARED or CALLABLE -->
      resultSetType="FORWARD_ONLY">
    

    statementType

    STATEMENT:

    1. 普通的不带参的查询SQL;
    2. 支持批量更新,批量删除;
    3. 由于每次都是拼接sql,在DB层面没有缓存的必要

    PREPARED:

    1. 可变参数的SQL,会预编译存储在DB的预编译缓存区域;编译一次,执行多次,效率高;
    2. sql代码可读性较高;
    3. 安全性好,有效防止Sql注入等问题;
    4. 支持批量更新,批量删除;

    CallableStatement:

    1. 继承自PreparedStatement,支持带参数的SQL操作;
    2. 支持调用存储过程,提供了对输出和输入/输出参数(INOUT)的支持;

    jdbcType:

    5814e721.png

    cache

    logging

    MyBatis provides logging information through the use of an internal log factory

    <configuration>
      <settings>
        ...
        <setting name="logImpl" value="LOG4J"/>
        ...
      </settings>
    </configuration>
    
    

    SLF4J, LOG4J, LOG4J2, JDK_LOGGING, COMMONS_LOGGING, STDOUT_LOGGING, NO_LOGGING or a full qualified class name that implements org.apache.ibatis.logging.Log


    基于Spring的mybatis

    dependency injection framework(DI)
    sqlSession直接由依赖注入框架直接创建,can create thread safe, transactional SqlSessions and mappers and inject them directly into your beans

    mybatis-spring
    in the Spring application context: an SqlSessionFactory and at least one mapper interface

    1. 创建一个挂接datasource的sqlSessionFactory(在spring下由SqlSessionFactoryBean代理)
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
          <property name="dataSource" ref="dataSource" />
        <!-- bean命名,通配 -->
        <property name="typeAliasesPackage" value="com.cssweb.*.model,com.gtja.*.model,com.cssweb.*.pojo,com.dzsw.*.model" />
        <!-- mapper.xml文件,通配 -->
        <property name="mapperLocations" value="classpath*:com/**/mapper/*Mapper.xml" />
        <!-- 日志转交log4j管理 -->
        <property name="configLocation" value="classpath:config/mybatis-config.xml"/> 
    </bean> 
    
    1. 定义一个mapper接口,可直接注入sql或者在xml文件
    //直接注入SQL
    public interface UserMapper {
      @Select("SELECT * FROM users WHERE id = #{userId}")
      User getUser(@Param("userId") String userId);
    }
    
    1. 将mapper接口加入spring,定义MapperFactoryBean
    <bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
      <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
      <property name="sqlSessionFactory" ref="sqlSessionFactory" />
    </bean>
    
    <!-- 替换3方案的方案是:MapperScannerConfigurer,可以使用通配符 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <property name="basePackage" value="com.*.*.mapper"></property>
    </bean>
    
    1. 由MapperFactoryBean对sqlsession进行管理,其异常传递到DataAccessExceptions

    2. DataSourceTransactionManager
      transactionManager allows MyBatis to participate in Spring transactions

    <!-- mybatis直接使用JDBC的事务管理 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="dataSource" />
    </bean>
    <!-- 事务驱动 -->
    <tx:annotation-driven transaction-manager="txManager"
            order="1" />
    
    1. mapper.xml
    <?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">
    
    <!-- namespace有用,和接口保持一致 -->
    <mapper namespace="com.gtja.forumApp.mapper.ForumAppMapper">
      <select id ="getEmployeeName" parameterType="String" resultType="String">
              select t.REAL_NAME creater from user_basic_info t where t.EMPLOYEE_ID=#{employeeId}
      </select>
    </mapper>
    

    注:

    • 在classpath后面的必不可少,缺少的话后面的通配符不起作用。
    • **表示可以表示任意多级目录
    • *表示多个任意字符
    • 一个通用的属性是 configLocation,它是用来指定 MyBatis 的 XML 配置文件路径的。 如果基本的 MyBatis 配置需要改变, 那么这就是一个需要它的地方。 通常这会是<settings> 或<typeAliases>的部分

    动态SQL

    #{}与${}的区别:#{}会根据当前值类型进行处理,而${}则替换当前位置,容易出现sql注入;

    sql注入:

    密码框中输入的是' ' or 1 = 1 加了一引号和一个恒成立的条件,那么,传到数据库中的sql就是:
    select count(1) from t_user where user_name = 'admin' and user_password = ' ' or 1=1

    动态关键字

    • if
    <select id="findActiveBlogWithTitleLike"
        parameterType="Blog" resultType="Blog">
        SELECT * FROM BLOG
        WHERE state = 'ACTIVE'
        <if test="title != null">
            AND title like #{title}
        </if>
    </select>
    

    用户条件设置sql

    • choose,when,otherwise
    int insertTest(@Param("a") String a, @Param("b") String b);
    
    <select id="insertTest"  parameterType = "String" resultType="int">
        select count(1) from gtja_sys_maintain t
         <trim prefix="WHERE" prefixOverrides="AND | OR ">
            <choose>
                <when test="a == '1'.toString()">
                 and t.id &gt; 368
                </when>
                <when test="b == '2'.toString()">
                    and t.id &lt; 466 
                </when>       
            </choose>
            </trim>
        </select>
    

    对于没匹配时,前置条件增加trim的prefix、prefixOverriders进行处理;
    对于单独的参数或者String,直接使用@Param注解进行匹配

    • set
    <update id="updateAuthorIfNecessary"
        parameterType="Author">
        update Author
        <trim prefix="where" prefixOverrides=",">
        <set>
            <if test="username != null">username=#{username},</if>
            <if test="password != null">password=#{password},</if>
            <if test="email != null">email=#{email}</if>
        </set>
        where id=#{id}
        </trim>
    </update>
    

    对于更新操作,使用set,同时针对逗号做处理

    • foreach
      主要有 item,index,collection,open,separator,close;
      collection属性:1、如果传入的是单参数且参数类型是一个List的时候,collection属性值为list;2、否则是array
    public class QueryVo(){
        private User user;
        private UserCustom userCustom;
        private List<integer> ids;
    }
    
    <select id="find" parameterType="qo" resultMap="userResult">
        select * from `user`
        <where>
            <foreach collection="ids" open=" and id in(" close=")" 
            item="id" separator=",">
                #{id}
            </foreach>
        </where>
        limit 0,10
    </select>
    
    
    <select id="selectByList" parameterType="java.util.List" resultType="user">
    select * from user 
    <where>
    <!-- 传递List,List中是pojo -->
    <if test="list!=null">
        <foreach collection="list" item="item" open="and id in("separator=","close=")">
              #{item.id} 
        </foreach>
    </if>
    </where>
    </select>
    
    <!-- 传递数组综合查询用户信息 -->
    <select id="findByArray" parameterType="Object[]" resultType="user">
    select * from user 
    <where>
    <!-- 传递数组 -->
    <if test="array!=null">
    <foreach collection="array" index="index" item="item" open="and id in("separator=","close=")">
                #{item.id} 
    </foreach>
    </if>
    </where>
    </select>
    
    • <sql id="queryCondition">将公共部分提取出来

    mybatis缓存

    分为一级缓存和二级缓存,通过缓存机制减轻数据压力,提高数据库性能。

    1. 一级缓存是SqlSession级别的缓存。默认开启
      如果SqlSession执行了DML操作(insert、update、delete),并执行commit()操作,mybatis则会清空SqlSession中的一级缓存,这样做的目的是为了保证缓存数据中存储的是最新的信息,避免出现脏读现象。
    2. 二级缓存时mapper级别的缓存,二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace

    原理:

    • Cache的key由haskcode+checksum+查询参数+sqlmap Id+sql语句+调用方法名等构成。各元素不同时都会产生有不同的Key
    • 定时刷新或受条件触发刷新Key
    • 4种Cache实现,但是无法自定义扩展Cache
    01c5e1a9.png
       <cacheModel id="maintainPojo-cache" type ="LRU" readOnly="true" serialize="false">  
           <flushInterval hours="24"/>
           <flushOnExecute statement="com.gtja.maintainManage.pojo.MaintainPojo.updateMaintainList"/>  
           <property value="500" name="size"/>  
        </cacheModel> 
    

    属性:

    • id:是cachemodel的一个标识
    • readOnly:指缓存的数据对象是只读还是可读写,默认只读
    • serialize:只有在readOnly为false的情况下才生效;默认false=》所有session共享Cache,取出时实例是同一个。不序列化,外部更改有效(即取出的bean,被java后续更改后,cache的key仍然有效)
    • flushInterval:指定缓存自动刷新的时间,可以为hours,minutes,seconds,milliseconds
    • flushOnExecute:指定在发生哪些操作时,更新缓存。一般用于增删改时
    • type: 缓存的类型。目前使用的缓存类型有四种:
      1. FIFO:com.ibatis.sqlmap.engine.cache.fifo.FifoCacheController 用先进先出的原则来确定如何从Cache中清除对象
      2. LRU:com.ibatis.sqlmap.engine.cache.fifo.LruCacheController 用近期最少使用的原则来确定如何从Cache中清除对象
      3. MEMORY:com.ibatis.sqlmap.engine.cache.fifo.MemoryCacheController 默认,大多数情况下是最好的选择,能大大提高查用查询的性能,但对于当前不被使用的查询结果数据,将被清除以释放内存来分配其他对象
      4. OSCACHE:com.ibatis.sqlmap.engine.cache.fifo.OSCacheController

    注意:对变更的数据,缓存时的实时性

    常见问题:

    1. 操作clob字段时:
    • 原因:有可能出现在当从dual中取数据时,oracle会将clob对象的字段转为Long型
    • 解决:参数,指定jdbcType=CLOB 类型;增加begin 和end;不能用到select from dual 的操作(merge不行)
    1. mybatis中使用merge时使用update关键词,而ibatis则使用statement关键词

    2. ibatis中map作为参数时报错

    • 原因:Map作为resultClass时,必须指定具体的实现类,比如java.util.HashMap,否则会报错;java.util.Map是接口
    • 解决:resultClass="java.util.HashMap"
    1. mybatis判断条件等于具体值时,加上.toString()
    <if test="newsImage != null and newsImage == '1'.toString()">  
        <![CDATA[ and len(newsImage) > 0 ]]>  
    </if>
    
    1. mybatis识别不了单个入参
    • 原因:默认采用ONGL解析参数,所以会自动采用对象树的形式取Integer.cmpid,但基本类型没有这个属性
    • 解决:1:参数名全部替换为_parameter;2:Campusinfo sel_campusinfo(@Param(value="cmpid") int cmpid);
    1. mybatis批量增删
    1. mybatis调用存储过程
    1. ibatis和mybatis的区别:
    • iBatis 中namespace 不是必需的,且它的存在没有实际的意义。在 MyBatis 中,namespace 终于派上用场了,它使得映射文件与接口绑定变得非常自然
    • ibatis使用resultClass,mybatis使用resultType
    1. mybatis 插入空值時需要指定jdbcType
    <insert id="save" parameterType="Province">
    <![CDATA[
    insert into t_yp_province
    (fid,fname,fnumber,fsimpleName,fdescription,fcreateTime,flastUpdateTime,fdirect)
    values
    ( #{id,jdbcType=VARCHAR},
    #{name,jdbcType=VARCHAR},
    #{number,jdbcType=VARCHAR},
    #{simpleName,jdbcType=VARCHAR},
    #{description,jdbcType=VARCHAR},
    #{createTime,jdbcType=DATE},
    #{lastUpdateTime,jdbcType=DATE},
    #{direct,jdbcType=NUMERIC}
    )
    ]]>
    </insert>;
    
    
    1. mybatis3.2版本在web容器启动时出现假死且不报错:
      解决:升级mybatis到3.4版本

    相关文章

      网友评论

          本文标题:Mybatis

          本文链接:https://www.haomeiwen.com/subject/ajgtnftx.html