Mybatis
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();
}
- Transaction use a transaction scope for the session, or use auto-commit
- Connection
- 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:
- 普通的不带参的查询SQL;
- 支持批量更新,批量删除;
- 由于每次都是拼接sql,在DB层面没有缓存的必要
PREPARED:
- 可变参数的SQL,会预编译存储在DB的预编译缓存区域;编译一次,执行多次,效率高;
- sql代码可读性较高;
- 安全性好,有效防止Sql注入等问题;
- 支持批量更新,批量删除;
CallableStatement:
- 继承自PreparedStatement,支持带参数的SQL操作;
- 支持调用存储过程,提供了对输出和输入/输出参数(INOUT)的支持;
jdbcType:
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
- 创建一个挂接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>
- 定义一个mapper接口,可直接注入sql或者在xml文件写
//直接注入SQL
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{userId}")
User getUser(@Param("userId") String userId);
}
- 将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>
-
由MapperFactoryBean对sqlsession进行管理,其异常传递到DataAccessExceptions
-
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" />
- 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 > 368
</when>
<when test="b == '2'.toString()">
and t.id < 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缓存
分为一级缓存和二级缓存,通过缓存机制减轻数据压力,提高数据库性能。
- 一级缓存是SqlSession级别的缓存。默认开启
如果SqlSession执行了DML操作(insert、update、delete),并执行commit()操作,mybatis则会清空SqlSession中的一级缓存,这样做的目的是为了保证缓存数据中存储的是最新的信息,避免出现脏读现象。- 二级缓存时mapper级别的缓存,二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace
原理:
- Cache的key由haskcode+checksum+查询参数+sqlmap Id+sql语句+调用方法名等构成。各元素不同时都会产生有不同的Key
- 定时刷新或受条件触发刷新Key
- 4种Cache实现,但是无法自定义扩展Cache
<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: 缓存的类型。目前使用的缓存类型有四种:
- FIFO:com.ibatis.sqlmap.engine.cache.fifo.FifoCacheController 用先进先出的原则来确定如何从Cache中清除对象
- LRU:com.ibatis.sqlmap.engine.cache.fifo.LruCacheController 用近期最少使用的原则来确定如何从Cache中清除对象
- MEMORY:com.ibatis.sqlmap.engine.cache.fifo.MemoryCacheController 默认,大多数情况下是最好的选择,能大大提高查用查询的性能,但对于当前不被使用的查询结果数据,将被清除以释放内存来分配其他对象
- OSCACHE:com.ibatis.sqlmap.engine.cache.fifo.OSCacheController
注意:对变更的数据,缓存时的实时性
常见问题:
- 操作clob字段时:
- 原因:有可能出现在当从dual中取数据时,oracle会将clob对象的字段转为Long型
- 解决:参数,指定
jdbcType=CLOB
类型;增加begin 和end;不能用到select from dual 的操作(merge不行)
-
mybatis中使用merge时使用update关键词,而ibatis则使用statement关键词
-
ibatis中map作为参数时报错
- 原因:Map作为resultClass时,必须指定具体的实现类,比如java.util.HashMap,否则会报错;java.util.Map是接口
- 解决:resultClass="java.util.HashMap"
- mybatis判断条件等于具体值时,加上.toString()
<if test="newsImage != null and newsImage == '1'.toString()">
<![CDATA[ and len(newsImage) > 0 ]]>
</if>
- mybatis识别不了单个入参
- 原因:默认采用ONGL解析参数,所以会自动采用对象树的形式取Integer.cmpid,但基本类型没有这个属性
- 解决:1:参数名全部替换为_parameter;2:Campusinfo sel_campusinfo(@Param(value="cmpid") int cmpid);
- mybatis批量增删
- mybatis调用存储过程
- ibatis和mybatis的区别:
- iBatis 中namespace 不是必需的,且它的存在没有实际的意义。在 MyBatis 中,namespace 终于派上用场了,它使得映射文件与接口绑定变得非常自然
- ibatis使用resultClass,mybatis使用resultType
- 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>;
- mybatis3.2版本在web容器启动时出现假死且不报错:
解决:升级mybatis到3.4版本