JavaWeb中Mybatis使用笔记

0 本文主要涉及

在基于 Spring 和 SpringMVC 的前后端分离的 JavaWeb 项目中使用 Mybatis,以及一些相关的笔记。

1 Mybatis简介

Mybatis官网:mybatis – MyBatis 3 | 简介
Mybatis-Spring官网:mybatis-spring –
MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架,它避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。​​​​相对 Hibernate “一站式”ORM 解决方案而言,Mybatis 是一种 “半自动化” 的 ORM 实现。MyBatis 可以对配置和原生 Map 使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects, 普通的 Java 对象) 映射成数据库中的记录。
Mybatis功能概述:动态SQL,入参和结果映射(处理数据库一对一、一对多的查询,使用存储过程的方法,处理存储过程的入参和出参方法,Java中的枚举方法和数据库表字段的处理等)

优点:
Mybatis 最大好处是在灵活度和方便性方面取得平衡,虽然“手工活儿”会多一些,但是你可以在需要的时候通过SQL换取灵活度和性能。 (引用自2gua)
MyBatis最核心的功能是动态SQL,它实现了优雅拼接SQL语句的方案,保持了使用SQL的优势,而其他的高级ORM和各种SQLBuilder都是徒劳了。
缺点:
每写一条功能逻辑需要涉及修改从接口到xml等多个文件
对缓存的支持不够好,尤其Mybatis-Spring的限制一级缓存是无效的,二级缓存由于缺乏合适的更新策略也不好用(设计多表的Mapper只能通过相同namesapce配置)。

2我的方案简介

在Spring相关框架下,通过第三方Mybatis工具库的支持(mapper,pagehelper,mybatis-generator等),做了一种可以自动生成每个单表增删改操作功能的方案,它同时支持维护复杂的其他DAL操作代码(将自动生成的代码和其他手写的代码区分到不同目录),将不需要手写的简单代码通过工具自动生成,而又不影响手写的其他逻辑代码。

3配置实现

0,pomxml

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.4</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.3.1</version>
</dependency>
<dependency>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-core</artifactId>
    <version>1.3.5</version>
</dependency>
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.0.2</version>
</dependency>
<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper</artifactId>
    <version>3.3.9</version>
</dependency>

1,Mybatis配置

<configuration>
    <!-- 参考:http://www.mybatis.org/mybatis-3/zh/configuration.html -->
    <settings>
        <!-- 所有映射器中配置的缓存的全局开关 -->
        <setting name="cacheEnabled" value="true"/>
        <!-- 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载 -->
        <setting name="aggressiveLazyLoading" value="true"/>
        <!-- 是否允许单一语句返回多结果集(需要兼容驱动) -->
        <setting name="multipleResultSetsEnabled" value="true"/>
        <!-- 使用列标签代替列名。不同的驱动在这方便表现不同。参考驱动文档或充分测试两种方法来决定所使用的驱动 -->
        <setting name="useColumnLabel" value="false"/>
        <!-- 允许JDBC支持生成的键。需要适合的驱动。如果设置为true则这个设置强制生成的键被使用,尽管一些驱动拒绝兼容但仍然有效(比如Derby) -->
        <setting name="useGeneratedKeys" value="true"/>
        <!-- 指定MyBatis如何自动映射列到字段/属性。PARTIAL只会自动映射简单,没有嵌套的结果。FULL会自动映射任意复杂的结果(嵌套的或其他情况) -->
        <setting name="autoMappingBehavior" value="PARTIAL"/>
        <!-- 配置默认的执行器。SIMPLE执行器没有什么特别之处。REUSE执行器重用预处理语句。BATCH执行器重用语句和批量更新 -->
        <setting name="defaultExecutorType" value="SIMPLE"/>
        <!--是否开启自动驼峰命名规则(camel case)映射-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!-- 设置超时时间,它决定驱动等待一个数据库响应的时间 -->
        <setting name="defaultStatementTimeout" value="25000"/>
        <!--返回resultType=”map”时,如果数据为空的字段,则该字段省略不显示,可以通过添加配置文件,规定查询数据为空是则返回null。 -->
        <setting name="callSettersOnNulls" value="false"/>
        <!--指定 MyBatis 所用日志的具体实现,未指定时将自动查找。-->
        <setting name="logImpl" value="Log4J"/>
        <setting name="logPrefix" value="MybatisLog->"/>
    </settings>

</configuration>

2Mybatis-Spring配置

 <!-- MyBatis -->
    <bean id="myBatisSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
 <!-- 多数据源时会有多个dataSource,同时也需要配置多个sqlSessionFactory来对应 -->
        <property name="dataSource" ref="omotDataSource"/>
        <property name="configLocation" value="classpath:MyBatisConfig/mybatis-config.xml"/>
        <property name="mapperLocations" value="classpath:Mapper/**/*.xml"/>
         <!-- 多路径-->
         <property name="mapperLocations">
            <array>
                <value>classpath:com/loongshawn/dao/impl/mapper/*.xml</value>
                <value>classpath:com/loongshawn/dao/impl/mapper3/pmc/*.xml</value>
            </array>
        </property>
        <property name="plugins">
            <array>
                <!--分页插件-->
                <bean class="com.github.pagehelper.PageInterceptor">
                    <!-- 这里的几个配置主要演示如何使用,如果不理解,一定要去掉下面的配置 -->
                    <property name="properties">
                        <value>
                            helperDialect=mysql
                            reasonable=true
                            supportMethodsArguments=true
                            params=count=countSql
                            autoRuntimeDialect=true
                        </value>
                    </property>
                </bean>
            </array>
        </property>
    </bean>
    <!--<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">-->
    <!--<property name="basePackage" value="com.test.application.**"/>-->
    <!--</bean>-->
    <!-- tk版本替代-->
    <bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
    <!--basePackage 属性多路径配置可以用,;分隔-->
        <property name="basePackage" value="com.test.application.**"/>
        <property name="sqlSessionFactoryBeanName" value = "myBatisSqlSessionFactory"></property>
        <property name="properties">
            <value>
                mappers=tk.mybatis.mapper.common.Mapper
            </value>
        </property>
    </bean>
    <bean id="mybatisSqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate" scope="prototype">
        <constructor-arg index="0" ref="myBatisSqlSessionFactory"/>
    </bean>

3Mybatis生成工具配置

添加了几个用于改变生成文件名称的插件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <!--<classPathEntry location="src/main/resources/lib/mysql-connector-java-5.1.38-bin.jar"/>-->
    <properties resource="setting.properties"/>
    <context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">
        <property name="javaFileEncoding" value="UTF-8"/>
        <!-- JavaBean 实现 序列化 接口 -->
        <plugin type="org.mybatis.generator.plugins.SerializablePlugin"/>
        <!-- genenat entity 时, 生成 toString -->
        <plugin type="org.mybatis.generator.plugins.ToStringPlugin"/>

        <!-- 表名映射实体类加后缀Entity-->
        <plugin type="com.test.common.db.myBatis.generatorTool.RenameBaseRecordPlugin">
            <property name="searchString" value="${db.table.prefix}#(.*?)$"/>
            <property name="replaceString" value="$1Entity"/>
        </plugin>
        <!-- 将接口后缀由Maper改为DAO-->
        <plugin type="com.test.common.db.myBatis.generatorTool.RenameJavaMapperPlugin">
            <property name="searchString" value="${db.table.prefix}#(.*?)Mapper$"/>
            <property name="replaceString" value="$1DAO"/>
        </plugin>
        <!--  表名映射xml名字-->
        <plugin type="com.test.common.db.myBatis.generatorTool.RenameSqlMapperPlugin">
            <property name="searchString" value="${db.table.prefix}#(.*?)$"/>
            <property name="replaceString" value="$1"/>
        </plugin>

        <!--通用maper处理插件-->
        <plugin type="tk.mybatis.mapper.generator.MapperPlugin">
            <property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
            <!-- caseSensitive默认false,当数据库表名区分大小写时,可以将该属性设置为true -->
            <property name="caseSensitive" value="true"/>
        </plugin>

        <commentGenerator>
            <!-- 是否去除自动生成的日期注释 true:是 : false: 否 -->
            <property name="suppressDate" value="true"/>
            <!-- 是否去除自动生成的注释 true:是 : false: 否 -->
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>


        <jdbcConnection driverClass="${db.driver}" connectionURL="${db.url}" userId="${db.username}" password="${db.password}"/>

        <javaModelGenerator targetPackage="com.test.application.autoGeneratedCode.Entity" targetProject="src/main/java"/>

        <sqlMapGenerator targetPackage="Mapper/AutoGenerated/" targetProject="src/main/resources"/>

        <javaClientGenerator targetPackage="com.test.application.autoGeneratedCode.DAO" targetProject="src/main/java" type="XMLMAPPER"/>

        <table tableName="${db.table.prefix}%">
            <!--generatedKey用于生成生成主键的方法-->
            <generatedKey column="id" sqlStatement="Mysql" identity="true"/>
            <!--Oracle 序列的配置方式, 这里的 {1} 代表的是对应表的大写形式,{0} 是小写形式 -->
            <!--<generatedKey column="id" sqlStatement="select SEQ_{1}.nextval from dual" identity="false" type="pre"/>-->
        </table>
    </context>
</generatorConfiguration>

4 封装后方案使用示例

涉及 事务处理,分页排序,MyBatis 处理集合、循环、数组,一对多和 in 查询等,以及各种sql xml标签的使用
普通的增删改(单表操作)已经通过封装实现,不再需要写接口和xml(通过自动生成工具生成)
简单介绍增删改相关方法:
public D insert(D recordBean)//插入后会回设主键值
public List<D> insertBatch(List<D> recordBeanList)
public D updateByPrimaryKey(D recordBean)
public D updateByUniqueField(String uniqueFieldName, D recordBean)
public long update(D recordBean, D filterBean)
public List<D> updateSelfBatch(List<D> recordBeanList, List<String> selfConditionFieldNameList) 
public long delete(D filterBean)
public long deleteByPrimaryKey(Object key) 
public long deleteIn(String fieldName, List filterFieldValueList)
也有select相关的接口不过select查单表意义不大。
事务处理:
第一种在类或方法上加@Transactional注解
第二种方法

//单条数据处理事务
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = transactionManager.getTransaction(def);
try {
	//数据库操作
    //。。。
	transactionManager.commit(status);
}
catch (Exception e) {
	logger.error("任务核查信息保存发生异常", e);
	logger.info("任务核查信息保存发生异常" + fkBsdjGjSjDbtbDO.toString());
	transactionManager.rollback(status);
}

分页排序:
通过第三方Mybatis插件实现,只需要在代码层处理,不需要将分页和排序相关参数一直从前端接口传到xml为止

OrderByHelper.orderBy("id DESC"));
PageHelper.startPage(query.getPageNum(), query.getPageSize());

xml中的占位符:
使用#{},防止sql注入,传入的参数是 pojo 类型,#{} 中的变量名称必须是 pojo 中的属性

XML中特殊符号处理:

注:尽量把 < 转换为 > 的条件写
第一种写法(1):
原符号       <        <=      >         >=       &           '            "
替换符号    &lt;    &lt;=   &gt;    &gt;=   &amp;   &apos;  &quot;
sql如下:
create_date_time &gt;= #{startTime} and  create_date_time &lt;= #{endTime}
第二种写法(2):
大于等于
<![CDATA[ >= ]]>
小于等于
<![CDATA[ <= ]]>
sql如下:
create_date_time <![CDATA[ >= ]]> #{startTime} and  create_date_time <![CDATA[ <= ]]> #{endTime}

MyBatis 处理集合、循环、数组, in 查询:

<select id="selectByWorkNos" parameterType="java.lang.String" resultMap="BaseResultMap">select 
  <include refid="Base_Column_List"/> from employee 
  <where>work_no 
    <choose> 
        <!--判断集合是否有值-->
      <when test="workNos!= null and workNos.length > 0">
        in 
        <foreach collection="workNos" item="workNo" open="(" close=")" separator=",">#{workNo,jdbcType=VARCHAR}</foreach> 
      </when>  
      <otherwise> <![CDATA[in (null)]]> </otherwise> 
    </choose> 
  </where> 
</select>

MyBatis 处理一带一:

<resultMap id="BaseResultMap" type="com.test.application.MapperDAO.AppBean"> 
  <id column="ID" jdbcType="INTEGER" property="id"/>  
  <result column="CODE" jdbcType="VARCHAR" property="code"/>  
  <result column="NAME" jdbcType="VARCHAR" property="name"/>  
  <!-- 也可以在此内部定义指向Author类的映射、此种定义不能重用 -->  
  <association property="lastStationInfo" javaType="com.test.application.autoGeneratedCode.Entity.InfoEntity"> 
    <id column="ID" jdbcType="INTEGER" property="id"/>  
    <result column="CODE" jdbcType="VARCHAR" property="code"/>  
    <result column="VALUE" jdbcType="DECIMAL" property="value"/> 
  </association> 
</resultMap>
<select id="complexQuery" parameterType="com.test.application.modules.example.InfoVO" resultMap="BaseResultMap">
    select * from t_app_info left join (select * from t_app_info order by t_app_info.value desc) temp on t_app.code = temp.code
</select>

MyBatis 处理一带多:


<mapper namespace="com.yiibai.userMaper">
	<!-- User 级联文章查询 方法配置 (一个用户对多个文章)  -->
	
	<resultMap type="User" id="resultUserMap">
		<result property="id" column="user_id" />
		<result property="username" column="username" />
		<result property="mobile" column="mobile" />
		<collection property="posts" ofType="com.yiibai.pojo.Post" column="userid">
			<id property="id" column="post_id" javaType="int" jdbcType="INTEGER"/>    
            <result property="title" column="title" javaType="string" jdbcType="VARCHAR"/>
            <result property="content" column="content" javaType="string" jdbcType="VARCHAR"/> 
		</collection>
	</resultMap>

	<select id="getUser" resultMap="resultUserMap" parameterType="int">
		SELECT u.*,p.*
		FROM user u, post p
		WHERE u.id=p.userid AND id=#{user_id} 
  </select>
</mapper>

一带一,一带多通过配置支持懒加载:

<!-- 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载 -->
<setting name="aggressiveLazyLoading" value="true"/>

test判断的坑:
单个的字符要写到双引号里面才行,改为<if test='takeWay == "1"'>或者改为<if test="takeWay == '1'.toString() ">
Mybatis 是用 OGNL 表达式来解析的,在 OGNL 的表达式中,’1’会被解析成字符,java 是强类型的,char 和 一个 string 会导致不等。

5其他的笔记

动态SQL标签总体简介:

·if if标签通常用于WHERE语句中,通过判断参数值来决定是否使用某个查询条件,它也经常用于UPDATE语句中判断是否更新某一个字段,还可以在INSERT语句中用来判断是否插入某个字段的值。
·choose(when、oterwise)实现if...else、if...else..的逻辑。choose元素中包含when和otherwise两个标签,一个choose中至少有一个when,有0个或者1个otherwise。
·trim(where、set)where 标签的作用:如果该标签包含的元素中有返回值,就插入一个where;如果where后面的字符串是以AND和OR开头的,就将它们剔除。set标签的作用:如果该标签包含的元素中有返回值,就插入一个set;如果set后面的字符串是以逗号结尾的,就将这个逗号剔除。trim标签有如下属性。
    ·prefix:当trim元素内包含内容时,会给内容增加prefix指定的前缀。
    ·prefixoverrides:当trim元素内包含内容时,会把内容中匹配的前缀字符串去掉。
    ·suffix:当trim元素内包含内容时,会给内容增加suffix指定的后缀。
    ·suffixoverrides:当trim元素内包含内容时,会把内容中匹配的后缀字符串去掉。
·foreachSQL 语句中有时会使用IN关键字,例如id in(1,2,3)。可以使用${ids}方式直接获取值,但这种写法不能防止SQL注入,想避免SQL注入就需要用#{}的方式,这时就要配合使用foreach标签来满足需求。foreach包含以下属性。
    ·collection:必填,值为要迭代循环的属性名。这个属性值的情况有很多。
    ·item:变量名,值为从迭代对象中取出的每一个值。
    ·index:索引的属性名,在集合数组情况下值为当前索引值,当迭代循环的对象是Map类型时,这个值为Map的key(键值)。
    ·open:整个循环内容开头的字符串。
    ·close:整个循环内容结尾的字符串。
    ·separator:每次循环的分隔符。

<insert id="insertList">
insert into sys user(
user name, user password, user email, user info, head img, create time)
values
<foreach collection="list"item="user"separator=",">
#{ user. userName},#{ user. userPassword},#{ user. userEmail},
#{ user. userInfo},#{ user. headImg, jdbcType=BLOB},
#{ user. createTime, jdbcType=TIMESTAMP})
</foreach>
</insert>

·bindbind标签可以使用OGNL表达式创建一个变量并将其绑定到上下文中。

<if test="userName!=null and userName!='1">
<bind name="userNameLike"value="'%'+userName +'%,"/>
and user name like#{userNameLike}
</if>

bind标签的两个属性都是必选项,name为绑定到上下文的变量名,value为OGNL表达式。创建一个bind标签的变量后,就可以在下面直接使用,使用bind拼接字符串不仅可以避免因更换数据库而修改SQL,也能预防SQL注入。

其他更多复杂的sql标签可以从官网学习使用。

结果集映射标签:

<resultMap id="userRoleMap" extends="userMap"
type="tk.mybatis.simple.model.SysUser">
<association property="role"columnPrefix="role "
resultMap="tk.mybatis.simple.mapper.RoleMapper.roleMap"/>
</resultMap>

association标签的嵌套查询常用的属性如下。
·select:另一个映射查询的id,MyBatis会额外执行这个查询获取嵌套对象的结果。
·column:列名(或别名),将主查询中列的结果作为嵌套查询的参数,配置方式如column={prop1=co11,prop2=co12},prop1和prop2将作为嵌套查询的参数。
·fetchrype:数据加载方式,可选值为lazy和eager,分别为延迟加载和积极加载,这个配置会覆盖全局的1azyLoadingEnabled配置。

<resultMap id="userRoleMapSelect"extends="userMap"
type="tk.mybatis.simple.model.SysUser">
<association property="role"
fetchType="lazy"
select="tk.mybatis.simple.mapper.RoleMapper.selectRoleById"
column="{id=role id}"/>
</resultMap>

在MyBatis的全局配置中,有一个参数为aggressiveLazyLoading。这个参数的含义是,当该参数设置为true时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载,反之,每种属性都将按需加载。
mybatis-config.xml中添加如下配置。

<settings>
<!--其他配置-->
<setting name="aggressiveLazyLoading"value="false"/>
</settings>

在和Spring集成时,要确保只能在Service层调用延迟加载的属性。当结果从Service层返回至Controller层时,如果
调用获取延迟加载的属性值,会因为SqlSession已经关闭而抛出异常。

<resultMap id="userRoleListMap" extends="userMap"
type="tk.mybatis.simple.model.SysUser">
<collection property="roleList"columnPrefix="role"
resultMap="tk.mybatis.simple.mapper.RoleMapper.roleMap"/>
</resultMap>

collection集合的嵌套结果映射和association类似,集合的嵌套结果映射就是指通过一次SOL查询将所有的结果查询出来,然后通过配置的结果映射,将数据映射到不同的对象中去。在一对多的关系中,主表的一条数据会对应关联表中的多条数据,因此一般查询时会查询出多个结果,按照一对多的数据结构存储数据的时候,最终的结果数会小于等于查询的总记录数。

鉴别器映射
有时一个单独的数据库查询会返回很多不同数据类型(希望有些关联)的结果集。
discriminator鉴别器标签就是用来处理这种情况的。鉴别器非常容易理解,因为它很像Java语言中的switch 语句。
discriminator标签常用的两个属性如下。
·column:该属性用于设置要进行鉴别比较值的列。
·javaType:该属性用于指定列的类型,保证使用相同的Java类型来比较值。
discriminator 标签可以有1个或多个case标签,case标签包含以下三个属性。
·value:该值为discriminator 指定column用来匹配的值。
·resultMap:当column的值和value的值匹配时,可以配置使用resultMap指定的映射,resultMap优先级高于resultType。
·resultrype:当column的值和value的值匹配时,用于配置使用resultrype指定的映射。
case标签下面可以包含的标签和resultMap一样,用法也一样。

<resultMap id="rolePrivilegeListMapChoose"
type="tk.mybatis.simple.model.SysRole">
<discriminator column="enabled"javaType="int">
<case value="1"resultMap="rolePrivilegelistMapSelect"/>
<case value="0"resultMap="roleMap"/>
</discriminator>
</resultMap>

角色的属性enable值为1的时候表示状态可用,为0的时候表示状态不可用。当角色可用时,使用roleprivilegeListMapselect映射,这是一个一对多的嵌套查询映射,因此可以获取到该角色下详细的权限信息。当角色被禁用时,只能获取角色的基本信息,不能获得角色的权限信息。

插入返回主键设置,两种方式:

适用于支持主键自增的数据库:

useGeneratedKeys 设置为true后,MyBatis会使用JDBC的getGeneratedKeys方法来取出由数据库内部生成的主键,获得主键值后将其赋值给keyProperty配置的id属性。当需要设置多个属性时,使用逗号隔开,这种情况下通常还需要设置keycolumn属性,按顺序指定数据库的列,这里列的值会和keyProperty配置的属性一一对应。由于要使用数据库返回的主键值,所以SOL上下两部分的列中去掉了id列和对应的#{id]属性。

动态SQL配置如下

<insert id="insert2"useGeneratedKeys="true"keyProperty="id">
insert into sys user(
user name,user password,user email, user info,head img,create time)
values(
#{userName},#{userPassword},#{userEmail},
#{userInfo},#{headImg,jdbcType=BLOB},
#{createTime,jdbcType=TIMESTAMP})
</insert>

适用于不提供主键自增的功能数据库(如Oracle):

使用序列得到一个值,然后将这个值赋给id,再将数据插入数据库。
使用<selectKey>标签来获取主键的值,这种方式不仅适用于不提供主键自增功能的数据库,也适用于提供主键自增功能的数据库。
selectKey 标签的keyColumn、keyProperty和上面useGeneratedKeys的用法含义相同,这里的resultType用于设置返回值类型。order属性的设置和使用的数据库有关,在MySQL数据库中,order属性设置的值是AFTER,因为当前记录的主键值在insert 语句执行成功后才能获取到。而在Oracle数据库中,order的值要设置为BEFORE,这是因为Oracle中需要先从序列获取值,然后将值作为主键插入到数据库中。

<insert id="insert3">insert into sys user(
user name,user password,user email, user info,head img,create time)
values(
#{userName},#{userPassword},#{userEmail},
#{userInfo},#{headImg,jdbcType=BLOB},
#{createTime,jdbcType=TIMESTAMP})
<selectKey keyColumn="id" resultType="long"keyProperty="id" order="AFTER">
SELECT LAST INSERT ID()
</selectKey>
</insert>

不同数据库区分配置:

mybatis-config.xml配置

<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server"value="sqlserver"/>
<property name="DB2"value="db2"/>
<property name="Oracle"value="oracle"/>
<property name="MySQL"value="mysql"/>
<property name="PostgresQL"value="postgresql"/>
<property name="Derby"value="derby"/>
<property name="HSQL" value="hsqldb"/>
<property name="H2" value="h2"/>
</databaseIdProvider>

动态SQL

<select id="SelectTime" resultType="String" databaseId="mysql">
   SELECT  NOW() FROM dual 
</select>

<select id="SelectTime"   resultType="String" databaseId="oracle">
   SELECT  'oralce'||to_char(sysdate,'yyyy-mm-dd hh24:mi:ss')  FROM dual 
</select>

当基于不同数据库运行时,MyBatis会根据配置找到合适的SQL去执行

存储过程调用示例:

<select id="selectUserById"statementType="CALLABLE"useCache="false">
{call select user by id(
#{id,mode=IN},
#{userName,mode=OUT,jdbcType=VARCHAR},
#{userPassword,mode=OUT,jdbcType=VARCHAR},
#{userEmail,mode=OUT,jdbcType=VARCHAR},
#{userInfo,mode=OUT,jdbcType=VARCHAR},
#{headImg,mode=OUT,jdbcType=BLOB,javaType=byte[]},
#{createTime,mode=OUT,jdbcType=TIMESTAMP}
)}
</select>

在调用存储过程的方法中,需要把statementType 设置为CALLABLE,在使用select标签调用存储过程时,由于存储过程方式不支持MyBatis的二级缓存,为了避免缓存配置出错,直接将 select标签的useCache属性设置为false。
在存储过程中使用参数时,除了写上必要的属性名,还必须指定参数的mode(模式),可选值为IN、OUT、INOUT三种。
入参使用IN,出参使用OUT,输入输出参数使用INOUT。
OUT模式的参数必须指定jdbcType。
IN模式,MyBatis提供了默认的jdbcType。
在使用Oracle数据库时,如果入参存在null的情况,那么入参也必须指定jdbcrype。

Java8日期支持:

MyBatis从3.4.0版本开始增加了对Java8日期(JSR-310)的支持。
如果使用3.4.0及以上版本,只需要在Maven的pom.xml中添加如下依赖即可
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-typehandlers-jsr310</artifactId>
<version>1.0.2</version>
</dependency>

缓存相关说明:

一级缓存

MyBatis的一级缓存存在于sqlsession的生命周期中,同一个SqlSession中执行的方法和参数完全一致,则会返回缓存中的对象。但是用于Mybatis-Spring实现的限制,除非是在同一个事务中否则,一级缓存是无效的。

二级缓存
(查询时先判断是否有二级缓存,在判断是否有一级缓存)
二级缓存配置
在MyBatis的全局配置settings中有一个参数cacheEnabled,这个参数是二级缓存的全局开关,默认值是true,初始状态为启用状态。如果把这个参数设置为false,即使有后面的二级缓存配置,也不会生效。
在保证二级缓存的全局配置开启的情况下,给RoleMapper.xml开启二级缓存只需要在UserMapper.xml中添加<cache/>元素即可,添加后的UserMapper.xml如下。
默认的二级缓存会有如下效果。
·映射语句文件中的所有SELECT语句将会被缓存。
·映射语句文件中的所有INSERT、UPDATE、DELETE语句会刷新缓存。
·缓存会使用Least Recently Used(LRU,最近最少使用的)算法来收回。
·根据时间表(如no Flush Interval,没有刷新间隔),缓存不会以任何时间顺序来刷新。
·缓存会存储集合或对象(无论查询方法返回什么类型的值)的1024个引用。
·缓存会被视为read/write(可读/可写)的,意味着对象检索不是共享的,而且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
cache可以配置的属性如下:
·eviction(收回策略)
>LRU(最近最少使用的):移除最长时间不被使用的对象,这是默认值。
>FIFO(先进先出):按对象进入缓存的顺序来移除它们。
>SoFT(软引用):移除基于垃圾回收器状态和软引用规则的对象。
>WEAK(弱引用):更积极地移除基于垃圾收集器状态和弱引用规则的对象。
·flushInterval(刷新间隔)。可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况不设置,即没有刷新间隔,缓存仅仅在调用语句时刷新。
·size(引用数目)。可以被设置为任意正整数,要记住缓存的对象数目和运行环境的可用内存资源数目。默认值是1024。
·readonly(只读)。属性可以被设置为true或false。只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,这提供了很重要的性能优势。可读写的缓存会通过序列化返回缓存对象的拷贝,这种方式会慢一些,但是安全,因此默认是false。

缓存引用
Mapper接口可以通过注解引用XML映射文件或者其他接口的缓存,在XML中也可以配置参照缓存,如可以在RoleMapper.xml中进行如下修改。
<cache-ref namespace="tk.mybatis.simple.mapper.RoleMapper"/>
这样配置后,XML就会引用Mapper接口中配置的二级缓存,同样可以避免同时配置二级缓存导致的冲突。

分部式场景使用缓存
MyBatis 默认提供的缓存实现是基于Map实现的内存缓存,已经可以满足基本的应用。但是当需要缓存大量的数据时,不能仅仅通过提高内存来使用MyBatis的二级缓存,还可以选择一些类似EhCache的缓存框架或Redis 缓存数据库等工具来保存MyBatis的二级缓存数据(实现Cache接口,并在xml cache标签中配置自定义的type)。接下来两节,我们会介绍两个常见的缓存框架。
当需要分布式部署应用时,如果使用MyBatis自带缓存或基础的EhCahca缓存,分布式应用会各自拥有自己的缓存,它们之间不会共享缓存,这种方式会消耗更多的服务器资源。如果使用类似Redis的缓存服务,就可以将分布式应用连接到同一个缓存服务器,实现分布式应用间的缓存共享。

二级缓存使用注意
二级缓存虽然能提高应用效率,减轻数据库服务器的压力,但是如果使用不当,很容易产生脏数据。这些脏数据会在不知不觉中影响业务逻辑,影响应用的实效,所以我们需要了解在MyBatis缓存中脏数据是如何产生的,也要掌握避免脏数据的技巧。
该如何避免脏数据的出现呢?这时就需要用到参照缓存了。当某几个表可以作为一个业务整体时,通常是让几个会关联的ER表同时使用同一个二级缓存,这样就能解决脏数据问题。
在上面这个例子中,将UserMapper.xml中的缓存配置修改如下。
<mapper namespace="tk.mybatis.simple.mapper.UserMapper">
<cache-ref namespace="tk.mybatis.simple.mapper.RoleMapper"/>
<!--其他配置-->
</mapper>
在以下场景中,推荐使用二级缓存:
·以查询为主的应用中,只有尽可能少的增、删、改操作。
·绝大多数以单表操作存在时,由于很少存在互相关联的情况,因此不会出现脏数据。
·可以按业务划分对表进行分组时,如关联的表比较少,可以通过参照缓存进行配置。除了推荐使用的情况,如果脏读对系统没有影响,也可以考虑使用。在无法保证数据不出现脏读的情况下,建议在业务层使用可控制的缓存代替二级缓存。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值