MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
MyBatis可以使用简单的 XML 或注解用于配置和原始映射,将接口和 Java 的 POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录。
JDBC– SQL 夹在 Java 代码块里,耦合度高导致硬编码内伤。 – 维护不易且实际开发需求中sql是有变化,频繁修改的情况多见。
// 大学的代码
public List<CompetitionInfo> ContestSearchByState(int state) {
JdbcUnit jdbcUnit = JdbcUnit.getInstance();
Connection con = jdbcUnit.createConn(); // 连接数据库
String sql = "Select b.CompetitionName,a.StartTime,a.EndTime,a.ContestState,a.ContestObject,a.ContestexamType " +"from competition b,contest a where a.CompetitionId = b.CompetitionId and " +"a.ContestState=" + state + " GROUP BY b.CompetitionName";
System.out.println("List<CompetitionInfo> ContestSearchByState(int state) 大赛首页根据赛事状态查找赛事列表: " + sql);
List<CompetitionInfo> competitionInfos = new ArrayList<CompetitionInfo>();
CompetitionInfo competitionInfo = null;
PreparedStatement ps = jdbcUnit.prepare(con, sql);
try {
ResultSet rs = ps.executeQuery(sql);
while (rs.next()) {
String CompetitionName=rs.getString(1);
String StartTime=rs.getString(2);
String EndTime=rs.getString(3);
int ContestState=rs.getInt(4);
int ContestObject=rs.getInt(5);
String ContestexamType=rs.getString(6);
competitionInfo=new CompetitionInfo(CompetitionName, StartTime, EndTime,ContestState,ContestObject,ContestexamType);
competitionInfos.add(competitionInfo);
}
con.close();
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
return competitionInfos;
}
复制代码
Hibernate 和 JPA– 长难复杂 SQL,对于 Hibernate 而言处理也不容易。 – 内部自动生产的 SQL,不容易做特殊优化。 – 基于全映射的全自动框架,大量字段的POJO进行部分映射时比较困难。导致数据库性能下降。
// webapi
public interface TransRecordService {
TransRecord getTransRecord(String enterpriseNum, String fgTradeNo);
// ....
List<TransRecord> findForActiveQuery(String enterpriseNum, String period);
boolean updateState(TransRecord transRecord, WebAPIPayStateEnum webAPIPayStateEnum);
boolean updateForQuery(String transRecordId, String state, String bankOrder, Date cycleDate);
// ....
boolean updateChannelType(String enterpriseNum, String fgTradeNo, String channelType);
void updateBankOrderNo(String enterpriseNum, String fgOrderNo, String bankOrderNo);
void updateCircPaymentNo(String enterpriseNum, String fgOrderNo, String circPaymentNo);
// ....
}
复制代码
<!-- Mybatis 非常轻量,核心只有一个包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
<!-- 对应的数据库连接包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.4.0</version>
</dependency>
<!-- 如果和 Spring 结合还需要适配包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.3</version>
</dependency>
复制代码
/**
* 1、创建 mybatis 配置文件
* 2、得到配置文件流
* 3、创建会话工厂,配置文件流
* 4、通过工厂得到 SqlSession
* 5、通过 SqlSession 操作数据库
* 6、释放资源
*/
@Test
public void test1() throws Exception {
String resource = "simple-mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 第一个参数:映射文件中statement的id,等于=namespace+"."+statement的id
// 第二个参数:指定和映射文件中所匹配的parameterType类型的参数
// sqlSession.selectOne 结果 是与映射文件中所匹配的 resultType 类型的对象
QueryOrgInfoReq orgInfoReq = QueryOrgInfoReq.builder().id(2).build();
OrgInfoPo po = sqlSession.selectOne("com.ariclee.mybatis.org.mapper.OrgInfoMapper.getBy", orgInfoReq);
System.out.println(po);
sqlSession.close();
}
复制代码
在 mabatis 中引入 .properties 文件
<properties resource="external.properties">
<!--<property name="username" value="dev_user"/>-->
<!--<property name="password" value="F2Fa3!33TYyg"/>-->
</properties>
复制代码
等后面用到时,再讲解。
类型别名,重命名为简短字符,降低冗余的全限定类名书写。 不区分大小写 。 常见的类型别名需要大概记一下:简单类型,首字母加下划线。包装类型,首字母小写。
使用 package 子标签可以批量指定别名
<typeAliases>
<package name="com.ariclee.mybatis.mapper"/>
</typeAliases>
复制代码
类型处理器,略过。
插件,略过。
配置不同的环境,使用 default 属性静态指定需要用到哪个环境。如:
<environments default="dev_oracle">
<environment id="dev_mysql">
<transactionManager type="jdbc"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://172.18.12.64:3306/spms?useSSL=false"/>
<property name="username" value="rhf"/>
<property name="password" value="rhf"/>
</dataSource>
</environment>
<environment id="dev_oracle">
<transactionManager type="jdbc"/>
<dataSource type="POOLED">
<property name="driver" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@10.60.45.207:1521:dbtest"/>
<property name="username" value="rhf"/>
<property name="password" value="rhf"/>
</dataSource>
</environment>
</environments>
复制代码
其中每个 environment 中子标签 transactionManager、dataSource 是必填项。
transactionManager:事务管理器 type 的可选项有 JDBC、MANAGED。
dataSource: type 可选项有 UNPOOLED|POOLED|JNDI。也可以自定义数据源类型:实现 DataSourceFactory 接口,返回自定义的 DataSource
数据库厂商标识。数据厂商标识可能比较繁琐,property 标签可以为厂商标识起别名。
<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" />
</databaseIdProvider>
复制代码
起好别名以后,就可以在 mapper 文件中引用,如下:
<select id="getBy" databaseId="mysql"
resultType="com.ariclee.mybatis.org.OrgInfoPo">
SELECT * FROM org_info_test where 1=1 and id=#{id}
</select>
<select id="getBy" databaseId="oracle"
resultType="com.ariclee.mybatis.org.OrgInfoPo">
SELECT * FROM org_info_test where 1=1 and id=#{id}
</select>
复制代码
当前 geyBy 方法在不同的数据库下有不同的实现。当连接的是 Oracle 数据库时,mybatis 会自动选择对应 SQL 实现。
把写好的 SQL 映射文件,注册进 Mybatis。
<mappers>
<mapper resource="./mapper/OrgInfoMapper.xml"/>
<mapper class="com.ariclee.mybatis.org.mapper.OrgInfoAnnotationMapper"/>
</mappers>
复制代码
当使用第一种 resource 形式注册时,mybatis 对 mapper 接口与 xml 文件的路径没有强制性要求,因为只要能定位映射文件,在映射文件中便可找到相对应的 mapper 接口。
但使用第二种 class 方式注册时,就有以下几点需要注意:
当 sql 映射文件存在多个时,可以使用 package 子标签,批量注册。
由于 name 属性中填的是类路径,当使用注解时映射语句与接口放在一起此时不会有问题。
而使用 xml 文件时,可能会存在 xml 文件与接口不在同一文件夹的情况。问题与上文使用 class 注册时,找不到映射文件类似, 为避免此情况,需要将 xml 文件与接口 .java 文件放在一起。(也可以放在 resources 文件下,建立对应 mapper 文件)
<mappers>
<package name="com.ariclee.mybatis.org.mapper"/>
</mappers>
复制代码
映射器的 XML 文件,是 mybatis 里面最重要的部分。xml 文件中包含以下几个顶级元素(没有顺序):
cache cache-ref resultMap sql insert update delete select
单个参数时,mybatis 不会做特殊处理,直接使用 #{} 取出,无论 {} 中填的是什么值。
**特殊:**如果参数是 List、Array、Set 等集合类型,mybatis 也会把其封装在 Map 中。 如果是 Collection 则 key 为 collect,List、Set 则会被更精确地封装为 list、set。如果是数组,则对应的 key 为 array。
多个参数时,mybatis 会将多个参数封装在 Map 中,key 为 param1...paramN,#{} 其实就是从 Map 中取值,大概如下:
<!-- OrgInfoPo getBy2(Integer id, String orgCode) -->
<select id="getBy2"
resultType="com.ariclee.mybatis.org.OrgInfoPo">
SELECT * FROM org_info_test where 1=1 and id=#{param1} and org_code=#{param2}
</select>
复制代码
但此种方法,可读性过差。此时就可以使用 @Param("") 注解来重命名 Map 的 key,于是就可以在 xml 中直接访问指定 key 的参数值,如下:
<!-- OrgInfoPo getBy2(@Param("id")Integer id, @Param("orgCode")String orgCode) -->
<select id="getBy2"
resultType="com.ariclee.mybatis.org.OrgInfoPo">
SELECT * FROM org_info_test where 1=1 and id=#{id} and org_code=#{orgCode}
</select>
复制代码
如果传入的参数是一个对象(VO、DTO、PO等),在映射文件中,则可以直接使用 #{属性名} 来访问(也是推荐做法);如果该对象是一个 Map,直接使用 #{MapKey} ,也可以访问。
下面是几个传参访问实例:
OrgInfoPo getBy2(@Param("id")Integer id, String orgCode)
OrgInfoPo getBy2(Integer id, OrgInfoPo po)
@Param 注解自定义 param key 其他的属性:javaType、jdbcType、mode(存储过程)、numericScale、resultMap、typeHandler(处理枚举)、jdbcTypeName、expression
尽管上面这些选项很强大,但大多时候,你只须简单指定属性名,顶多要为可能为空的列指定 jdbcType,其他的事情交给 MyBatis 自己去推断就行了。
对于数值类型,还可以设置 numericScale 指定小数点后保留的位数。
#{height,javaType=double,jdbcType=NUMERIC,numericScale=2}
复制代码
对于 Oracle 环境下无法识别 null 值处理
#{orgName, jdbcType=VARCHAR}
<!-- setting -->
<settings>
<setting name="jdbcTypeForNull" value="NULL"/>
</settings>
复制代码
与 #{} 的区别:
#{}
${}
<!-- 配置 -->
<select id="getBy3"
resultType="com.ariclee.mybatis.org.OrgInfoPo">
SELECT * FROM org_info_test where 1=1 and id=${id} and org_code=#{orgCode}
</select>
<!-- 最终执行的 SQL -->
SELECT * FROM org_info_test where 1=1 and id=2 and org_code=?
复制代码
<insert> 标签 <select> 标签 | 属性 | 描述 |
|---|---|
| id | 在命名空间中唯一的标识符,可以被用来引用这条语句。 不能存在同名方法,即使方法的参数不一样 |
| parameterType | 将会传入这条语句的参数的类全限定名或别名。 可选 因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。 |
| resultType | 期望从这条语句中返回结果的类全限定名或别名。注意, 如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。 |
| resultMap | 对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。 |
| flushCache | 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。 |
| useCache | 将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。 |
| timeout | 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。 |
| fetchSize | 这是一个给驱动的建议值,尝试让驱动程序每次批量返回的结果行数等于这个设置值。 默认值为未设置(unset)(依赖驱动)。 |
| statementType | 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。 |
| resultSetType | FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖数据库驱动)。 |
| databaseId | 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。 |
| resultOrdered | 这个设置仅针对嵌套结果 select 语句:如果为 true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false。 |
| resultSets | 这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。 |
resultType:自动封装;resultMap:自定义 javaBean 封装规则,高级结果集映射 - resultType 比较简单,如果列名和类字段名能对应上或者是符合自动映射规则,则使用 resultType。若对不上,或者需要使用更加强大的映射功能选择 resultMap - 查询语句两者必须指定一个,但不能同时指定 log Caused by: org.apache.ibatis.executor.ExecutorException: A query was run and no Result Maps were found for the Mapped Statement 'com.ariclee.mybatis.org.mapper.OrgInfoMapper.getBy3'. It's likely that neither a Result Type nor a Result Map was specified.
<select id="getBy4" resultType="map">
SELECT * FROM org_info_test where 1=1 and id=#{id}
</select>
复制代码
@MapKey("id")
Map<String, OrgInfoPo> getBy5(String orgCode);
<select id="getBy5" resultType="com.ariclee.mybatis.org.OrgInfoPo">
SELECT * FROM org_info_test where 1=1 and org_code like #{orgCode}
</select>
复制代码
假设一个组织下面只挂一个商户
public class OrgWithMchInfoPo {
private MchInfoPO mch;
}
复制代码
映射文件 resultMap 写法
<!-- 对象访问符实现 -->
<resultMap id="OrgInfoPoWithMchInfoMap1" type="com.ariclee.mybatis.org.OrgWithMchInfoPo">
<id property="id" column="id"/>
<result column="mit_id" property="mch.id"/>
<result column="mit_code" property="mch.code"/>
<result column="mit_name" property="mch.name"/>
</resultMap>
<!-- association 标签实现 -->
<resultMap id="OrgInfoPoWithMchInfoMap1" type="com.ariclee.mybatis.org.OrgWithMchInfoPo">
<id property="id" column="id"/>
<association property="mch" javaType="com.ariclee.mybatis.mch.MchInfoPO">
<result column="mit_id" property="id"/>
<result column="mit_code" property="code"/>
<result column="mit_name" property="name"/>
</association>
</resultMap>
<select id="getBy6" resultMap="OrgInfoPoWithMchInfoMap1">
SELECT
oit.id as id,
oit.org_code as org_code,
oit.org_name as org_name,
mit.id as mit_id,
mit.code as mit_code,
mit.name as mit_name
FROM org_info_test oit left join mch_info_test mit on oit.id=mit.org_id
where 1=1 and oit.id=#{id}
</select>
复制代码
<association> 实现分步查询+延时查询 <resultMap id="OrgInfoPoWithMchInfoMap1_1" type="com.ariclee.mybatis.org.OrgWithMchInfoPo">
<id property="id" column="id"/>
<association property="mch"
javaType="com.ariclee.mybatis.mch.MchInfoPO"
select="getMchInfoByOrgId" column="id" fetchType="lazy">
</association>
</resultMap>
<!-- 主方法 -->
<select id="getBy6_1" resultMap="OrgInfoPoWithMchInfoMap1_1">
SELECT *
FROM org_info_test
where 1=1 and id=#{id}
</select>
<!-- 根据组织 id 查询商户 -->
<select id="getMchInfoByOrgId" resultType="com.ariclee.mybatis.mch.MchInfoPO">
SELECT *
FROM mch_info_test
where 1=1 and org_id=#{id}
</select>
复制代码
当书写完分步查询 SQL 映射文件后,需要使用全局配置 lazyLoadingEnabled 开关或者使用 association fetchType 属性来指定。官网解释如下:
lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。
<association property="mch"
javaType="com.ariclee.mybatis.mch.MchInfoPO"
select="getMchInfoByOrgId" column="{orgId=id,orgId=id}" fetchType="lazy">
</association>
复制代码
假设一个组织下面挂了多个商户
public class OrgWithMchInfoPo2 {
private List<MchInfoPO> mchs;
}
复制代码
连表一次查询
<resultMap id="OrgInfoPoWithMchInfoMap2" type="com.ariclee.mybatis.org.OrgWithMchInfoPo2">
<id property="id" column="id"/>
<collection property="mchs" ofType="com.ariclee.mybatis.mch.MchInfoPO">
<id column="mit_id" property="id"/>
<result column="mit_code" property="code"/>
<result column="mit_name" property="name"/>
</collection>
</resultMap>
复制代码
同样的, collection 也支持分段查询和延时加载 ,映射文件 resultMap 写法
<resultMap id="OrgInfoPoWithMchInfoMap2" type="com.ariclee.mybatis.org.OrgWithMchInfoPo2">
<id property="id" column="id"/>
<collection property="mchs" select="getMchInfosByOrgId" column="id" />
</resultMap>
复制代码
使用 channel 和 channel_param 的例子
discriminator 鉴别器 判断某列的值,改变封装行为。例子如下:当 id 为 1 时,查出关联的商户信息;id 为 2 时,对 orgName 字段赋值。
<resultMap id="OrgInfoPoWithMchInfoMap3" type="com.ariclee.mybatis.org.OrgWithMchInfoPo">
<id property="id" column="id"/>
<discriminator javaType="integer" column="id">
<case value="1">
<association property="mch"
javaType="com.ariclee.mybatis.mch.MchInfoPO"
select="getMchInfoByOrgId" column="id" >
</association>
</case>
<case value="2">
<result property="orgName" column="org_name"/>
</case>
</discriminator>
</resultMap>
<select id="getBy8" resultMap="OrgInfoPoWithMchInfoMap3">
SELECT * FROM org_info_test where 1=1 and id=#{id}
</select>
复制代码
使用 mch_info 和 xxx_mch_info 的例子
<update> 标签 <update id="update1">
update org_info_test set org_name = #{orgName} where id=#{id}
</update>
复制代码
<delete> 标签 <!-- 单笔删除 -->
<delete id="delete1">
delete from org_info_test where id = #{id}
</delete>
<!-- 批量删除 -->
<delete id="batchDelete1">
delete from org_info_test where id in
<foreach collection="ids" separator="," item="id" open="(" close=")">
#{id}
</foreach>
</delete>
复制代码
如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。
动态 SQL 包含的标签: if、choose (when, otherwise)、trim (where, set)、foreach
官方文档: commons.apache.org/proper/comm…
常用语法:
使用场景:
<if test=""> 、 <bind value=""> ${} 参数中,在 #{} 中不适用 注意 XML 文档中的转义字符 " & 等
<if> 标签 条件后面 and 问题解决方案:
<where>
在条件字符串前面加上 where 字符串,并且覆盖后面的 and 字符
<trim prefix="where" prefixOverrides="" suffix="" suffixOverrides="and">
<if test="orgCodeLike!=''">
org_code like #{orgCodeLike} and
</if>
</trim>
复制代码
<choose> 标签 当满足一个条件时,会立即 break,不会再执行下面的操作。当同时满足 orgCodeLike!='' 和 id != null 时,id 条件不会被拼接。
<select id="getBy12" resultType="com.ariclee.mybatis.org.OrgWithMchInfoPo">
SELECT * FROM org_info_test
<where>
<choose>
<when test="orgCodeLike!=''">
and org_code like #{orgCodeLike}
</when>
<when test="id != null">
id=#{id}
</when>
</choose>
</where>
</select>
复制代码
<set> 标签 where 用来封装查询条件, set 来封装更新条件。 本质为了去掉更新字段后面多出的逗号问题
<update id="update3">
update org_info_test
<set>
<if test="orgCode != ''">
org_code=#{orgCode},
</if>
<if test="orgName != ''">
org_name=#{orgName},
</if>
<if test="createTime != null">
create_time=#{createTime},
</if>
</set>
where id=#{id}
</update>
复制代码
同样也可以使用 <trim> 来达到相同的目的
<update id="update3">
update org_info_test
<trim prefix="set" prefixOverrides="" suffix="" suffixOverrides=",">
<if test="orgCode != ''">
org_code=#{orgCode},
</if>
<if test="orgName != ''">
org_name=#{orgName},
</if>
<if test="createTime != null">
create_time=#{createTime},
</if>
</trim>
where id=#{id}
</update>
复制代码
foreach 标签 <delete id="batchDelete1">
delete from org_info_test where id in
<foreach collection="ids" separator="," item="id" open="(" close=")">
#{id}
</foreach>
</delete>
复制代码
<insert id="batchAdd1">
<!-- 方式一 -->
insert into org_info_test(org_code, org_name, create_time) values-->
<foreach collection="pos" item="po" separator=",">
(#{po.orgCode}, #{po.orgName}, #{po.createTime})
</foreach>
<!-- 需要手动开启,不仅可以批量保存,还可以批量更新 -->
<!-- mysql allowmultiqueries -->
<foreach collection="pos" item="po" separator=";">
insert into org_info_test(org_code, org_name, create_time) values
(#{po.orgCode}, #{po.orgName}, #{po.createTime})
</foreach>
</insert>
复制代码
<insert id="batchAdd2">
begin
<foreach collection="pos" item="po">
insert into org_info_test (id, org_code, org_name, create_time)
values(t_org_info_test_seq.nextval, #{po.orgCode}, #{po.orgName}, #{po.createTime});
</foreach>
end;
</insert>
复制代码
第二种临时比较复杂,省略;
_paramter 、 _databaseId _paramter:封装好的参数 Map。增加为 null 判断,提高鲁棒性
<select id="getBy13" resultType="com.ariclee.mybatis.org.OrgWithMchInfoPo">
SELECT * FROM org_info_test where 1=1
<if test="_parameter != null">
and id=#{id}
</if>
</select>
复制代码
bind 标签 <select id="getBy14" resultType="com.ariclee.mybatis.org.OrgWithMchInfoPo">
<bind name="_orgCodeLike" value="'%'+orgCodeLike+'%'" />
SELECT * FROM org_info_test where 1=1
and org_code like #{_orgCodeLike}
</select>
复制代码
sql 标签 抽取可重用的 SQL 片段。使用 include 标签引用。
<sql id="common_condition">
1=1
<if test="appCodeList != null and appCodeList.size()>0">
and application_code in
<foreach collection="appCodeList" open="(" separator="," close=")" item="appCodeItem">#{appCodeItem}
</foreach>
</if>
</sql>
<select id="queryOverviewPageData" resultMap="OverviewPageMap">
select
sum(cost) as cost
from daily_trans_report
where <include refid="common_condition"/>
</select>
复制代码
MyBatis 包含一个非常强大的查询缓存特性,且非常方便地配置和定制。缓存可以极大的提升查询效率。MyBatis 框架中默认定义了两级缓存:一级缓存和二级缓存。
一级缓存(local cache),即本地缓存,作用域默认为 sqlSession 。**当 Session flush 或 close 后, 该 Session 中的所有 Cache 将被清空。**可以调用 clearCache() ,来清空本地缓存。当全局配置 localCacheScope 值为 STATEMENT 时,相当于禁用掉了一级缓存。
一级缓存失效的四种情况:
二级缓存(second level cache),全局作用域缓存 namespace 级别。
cache 标签的属性
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/> 复制代码
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。可用的清除策略有:
FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
flushInterval(刷新间隔)属性:可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
size(引用数目)属性:可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
readOnly(只读)属性:可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
开启二级缓存步骤:
<cache />
与其他标签中缓存配置的相互作用:
select 标签中 useCache 打开和关闭的是二级缓存 insert 、 update 、 delete 标签中 flushCache 默认是打开的,且对一级、二级缓存都有影响 sqlSession.clearCache() 该方法只会影响当前一级缓存 localCacheScope 默认值为 SESSION(一级缓存开启),配置为 STATEMENT,相当于禁用掉了一级缓存 官方文档: mybatis.org/spring/gett…
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.3</version>
</dependency>
复制代码
<!-- 数据源配置 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" >
<property name="url" value="jdbc:mysql://172.18.12.64:3306/spms?useSSL=false"/>
<property name="username" value="rhf"/>
<property name="password" value="rhf"/>
</bean>
<!-- 事务管理器配置 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 注解 -->
<tx:annotation-driven transaction-manager="txManager"/>
复制代码
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config-with-spring.xml"/>
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
<!-- 推荐方式 -->
<mybatis:scan factory-ref="sqlSessionFactory" base-package="com.ariclee.mybatis.org.mapper"/>
<!-- 老版本方式 -->
<!--<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">-->
<!--<property name="basePackage" value="com.ariclee.mybatis.org.mapper"/>-->
<!--<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>-->
<!--</bean>-->
复制代码
官方文档: github.com/pagehelper/…
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.5</version>
</dependency>
复制代码
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"/>
</plugins>
复制代码
// 使用返回值获取分页信息
Page<Object> page = PageHelper.startPage(1, 3);
List<OrgWithMchInfoPo> res1 = mapper.getBy16();
res1.forEach(System.out::println);
System.out.println("总页数:" + page.getPages());
System.out.println("总条数:" + page.getTotal());
// 使用 PageInfo 包装后获取分页信息
PageHelper.startPage(1, 3);
List<OrgWithMchInfoPo> res1 = mapper.getBy16();
PageInfo<OrgWithMchInfoPo> pageInfo = new PageInfo<>(res1);
res1.forEach(System.out::println);
System.out.println("总页数:" + pageInfo.getPages());
System.out.println("总条数:" + pageInfo.getTotal());
复制代码
BaseTypeHandler 接口 下面是一个通用的枚举处理器
@MappedTypes(value = {
SceneEnum.class
})
public class EnumValueTypeHandler<E extends LabelAndValue> extends BaseTypeHandler<E> {
private Class<E> type;
private final E[] enums;
public EnumValueTypeHandler(Class<E> type) {
if (type == null) {
throw new IllegalArgumentException("Type argument cannot be null");
}
this.type = type;
this.enums = type.getEnumConstants();
if (this.enums == null) {
throw new IllegalArgumentException(type.getSimpleName() + " does not represent an enum type.");
}
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter.getValue());
}
@Override
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
String value = rs.getString(columnName);
if (rs.wasNull()) {
return null;
}
else {
try {
return getEnumByValue(value);
} catch (Exception ex) {
throw new IllegalArgumentException(
"Cannot convert " + value + " to " + type.getSimpleName() + " by ordinal value.", ex);
}
}
}
protected E getEnumByValue(String value) {
for (E e : enums) {
if (e.getValue().equals(value)) {
return e;
}
}
return null;
}
@Override
public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String i = rs.getString(columnIndex);
if (rs.wasNull()) {
return null;
} else {
try {
return getEnumByValue(i);
} catch (Exception ex) {
throw new IllegalArgumentException(
"Cannot convert " + i + " to " + type.getSimpleName() + " by ordinal value.", ex);
}
}
}
@Override
public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String i = cs.getString(columnIndex);
if (cs.wasNull()) {
return null;
} else {
try {
return getEnumByValue(i);
} catch (Exception ex) {
throw new IllegalArgumentException(
"Cannot convert " + i + " to " + type.getSimpleName() + " by ordinal value.", ex);
}
}
}
}
复制代码
<!-- 全局指定 -->
<typeHandlers>
<typeHandler javaType="com.ariclee.mybatis.org.SceneEnum" handler="com.ariclee.mybatis.org.EnumValueTypeHandler"/>
</typeHandlers>
<!-- 在使用时指定 -->
<resultMap id="OrgInfoMap" type="com.ariclee.mybatis.org.OrgInfoPo">
<result column="scene" property="scene" javaType="com.ariclee.mybatis.org.SceneEnum" typeHandler="com.ariclee.mybatis.org.EnumValueTypeHandler"/>
</resultMap>
<insert id="insertWithEnum">
insert into org_info_test (org_code, org_name, create_time, scene)
values(#{orgCode}, #{orgName}, #{createTime}, #{scene, typeHandler=com.ariclee.mybatis.org.EnumValueTypeHandler});
</insert>
复制代码