全局配置 mybatis-config.xml
porperties属性
可以配置一些运行参数,可以放一些porperties或者xml文件,比如:
<properties>
<property resource = "db.properties"></property>
</properties>
settings设置
能够配置的内容比较多,能够影响Mybatis底层的运行,一般只修改一些常用的规则,比如自动映射、驼峰命名规则、级联规则、缓存、Executor类型等,比如:
<settings>
<!--打印查询语句 -->
<setting name = "logImpl" value = "STDOUT_LOGGING"/>
<!--延迟加载开关,解决N+1问题,默认值为false-->
<setting name = "lazyLoadingEnabled" value = "true"/>
<!--层级加载开关,默认值3.4.1之后为false-->
<setting name = "aggresiveLazyLoading" value = "true" />
<!--一级缓存开关,默认值session,也就是开启,修改值为STATEMENT则会关闭一级缓存-->
<setting name = "localCacheScope" value = "SESSION"/>
<!--二级缓存开关,默认开启,另外也可以在映射器中使用<cache/>标签进行开启,在特定的Sql标签修改useCache为false可以关闭该statement的二级缓存-->
<setting name = "cacheEnabled" value = "true"/>
</settings>
typeAliases类型别名
定义类的别名,简化使用,通常使用扫描的方式,如果别名重复使用注解@Aliases进行区分,比如:
<typeAliases>
<typeAlias alias = "use" type = "com.xxx.User"></typeAlias>
<package name = "com.xxx"/>
</typeAliases>
typeHandlers类型转换器
将Java类型和Jdbc类型进行转换,有系统定义和自定义,自定义的需要实现BaseTypeHandler接口并且在这里进行注册,或者在映射器中指定,比如:
<!-- 配置文件中指定 -->
<typeHandlers>
<typeHandler handler = "com.xxx.typehandler.SexTypeHandler" javaType = "com.xxx.pojo.SexEnum" jdbcType = "INTEGER"/>
<package name = "com.xxx.typehandler"/>
</typeHandlers>
<!-- 映射器中指定-->
<resultMap id = "userResultMap" type = "user">
<result property = "sex" column = "sex" typeHandler = "com.xxx.typehandler.SexTypeHandler"></result>
</resultMap>
objectFactory对象工厂
对象工厂是在创建结果集时通过反射来创建实例对象,可以通过实现ObjectFactory接口或者继承DefaultObjectFactory来自定义返回规则,通常不需要配置,
<ObjectFactory type = "com.xxx.objectfactory.MyObjectFactory">
<property name = "" value = ""></property>
</ObjectFactory>
plugin插件
Mybatis中的插件拦截的四个对象Executor、ParameterHandler、ResultSetHandler、StatementHandler,自定义的插件需要实现Interceptor接口,并且在配置中注册。关于插件的运行原理和分页插件的使用在后续文章中在写。
enviroments运行环境
主要是配置数据库信息,可以配置多个数据库,一般而言只需要配置一个就行了。包括了事务管理器和数据源
事务管理器:Mybatis提供了两个工厂类,:JdbcTransactionFactory和ManagedTransactionFactory,JDBC以jdbc的方式对数据库的提交和回滚进行操作,而MANAGED把事务交给容器处理,提交和回滚方法不用任何操作,默认情况下会关闭连接。
数据源:提供了PooledDataSourceFactory、UnpooledDataSourceFactory、JndiDataSourceFactory三个工厂类,POOLED使用连接池的思想对Connection管理;UNPOOLED采用非数据库连接池的管理方式,每次请求都会打开一个新的数据库连接;JNDI是为了能在如EJB或者应用服务器这类容器中使用。
在与Spring集成之后数据库连接和事务都托管给了Spring。
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value=""/>
<property name="url" value=""/>
<property name="userName" value=""/>
<property name="password" value=""/>
</dataSource>
</environment>
</environments>
- databaseIdProvider数据库厂商标识
查询时可以支持多数据库,当SQL语句中的databaseID被配置了的时候,系统会优先取和数据库配置一致的SQL,如果没有,则取没有databaseID的SQL执行,可以把它当做默认值,value为数据库别名,可以通过这个别名标注那一条SQL语句适用于哪种数据库运行,比如:
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql"/>
<property name="Oracle" value="oracle"/>
<property name="DB2" value="db2"/>
</databaseIdProvider>
<select id = "getUserById" parameterType = "int" resultType = "user" databaseId = "mysql">
select id uname as name note from t_user where 1 = 1 and id = #{id}
</select>
<select id = "getUserById" parameterType = "int" resultType = "user" databaseId = "oracle">
select id uname as name note from t_user where id = #{id}
</select>
<!-- 这样当数据库切换时,就会自动选择对应的sql进行查询-->
- mapper映射器
映射器namespace = 接口的权限名,SELECT|UPDATE|INSERT|DELETE标签的id则对应接口的方法名,映射器的引入通常有下面几种方法:
<mappesr>
<!--使用类名注册-->
<mapper class = "com.xxx.mapper.UserMapper"/>
<!--使用包名注册,常用-->
<package name = "com.xxx.mapper"/>
<!--使用文件路径引入-->
<mapper resource = "com/xxx/mapper/UserMapper.xml"/>
</mappers>
和spring集成之后映射器的扫描托管到spring的配置文件中,在springboot中则使用注解@MapperScan(“packagename”)进行扫描。
Mapper映射器
缓存cache
跟Hibernate一样,Mybatis也有一级缓存和二级缓存,并且预留了集成第三方缓存的接口。
Mybatis跟缓存相关的类都在cache包中,定义了一个Cache接口,并且只有一个默认的实现类PerpetualCache底层使用HashMap实现,这个对象一定会创建,也叫基础缓存,除此之外,Mybatis还定义了很多缓存的装饰器,通过这些装饰器,可以对基础缓存扩展很多额外的功能,比如回收策略、日志记录、定时刷新等。总体上分三类:基本缓存,淘汰算法缓存、装饰器缓存。下面对这些缓存做个说明:
缓存实现类 | 描述 | 作用 | 装饰条件 |
基本缓存 | 缓存基本实现类 | 默认是PerpetualCache,也可以自定义比如RedisCache/EhCache等具备基本功能的缓存类 | 无 |
LruCache | LRU策略的缓存 | 删除最近最少使用的缓存 | eviction=“LRU”(默认) |
FifoCache | FIFO策略的缓存 | 删除最先入队的缓存 | eviction=“FIFO” |
SoftCache WeakCaceh | 带清理策略的缓存 | 通过JVM的软引用和弱引用来实现缓存,当JVM内存不足时,会自动清理这些缓存,基于SoftReference和WeakReference | eviction=“SOFT|WEAK” |
LoggingCache | 带日志功能的缓存 | 比如:输出缓存命中率 | 基本 |
SynchronizedCache | 同步缓存 | 基于Synchronized关键字实现,解决并发问题 | 基本 |
BlockingCache | 阻塞缓存 | 通过在get/put方式中加锁,保证只有一个线程操作缓存,基于JAVA重入锁实现 | bloking=“true” |
SerializedCache | 支持序列化的缓存 | 将对象序列化以后存入缓存,取出时反序列化 | readOnly=false(默认) |
ScheduledCache | 定时调度的缓存 | 在进行get/put/remove/getSize等操作前,判断缓存时间是否超过了设置的最长缓存时间(默认是1小时),如果是则清空缓存,也就是每隔一段时间清空一次缓存 | flushInterval不为空 |
TransactionalCache | 事务缓存 | 在二级缓存中使用,可一次存入多个缓存,移除多个缓存 | 在TransactionalCacheManager中用Map维护对应关系 |
- 一级缓存
一级缓存也叫本地缓存Local Cache,是SqlSession层面的缓存,默认开启。既然是在SqlSession中进行缓存,DefaultSqlSession中只有两个对象属性:Configuration和Executor,而前者是全局性的,所以一级缓存是在Executor中维护的,三种执行器的父类BaseExecutor中的构造函数中就初始化了PerPetual对象。 - 在同一个Session中共享,不同Session不能共享。
一级缓存的存入:是在BaseExecutor的query()方法中的queryFromDataBase()方法中存入的。
一级缓存的清空:同一个会话中,更新或者删除操作会导致缓存被清空,如果select标签的flushCache=true时,则查询也会清空缓存。在BaseEexcutor中的update()方法中调用了clearLocalCache()方法来清空。 - 一级缓存存在的问题:如果其他会话更新数据,会导致本会话从缓存中读取到过时的数据。要解决这个问题只能用范围更大的二级缓存。
- 二级缓存
二级缓存是用来解决一级缓存不能跨会话共享的问题的,是namespace级别的,只要是同一个接口里面相同的方法,都可以共享,声明周期和应用同步。
如果开启了二级缓存,那么使用到缓存的时候,肯定是先查询二级缓存,二级缓存没有的时候才去一级缓存中查,一级缓存没有的时候就到数据库去查。
一级缓存是放在Executor中的,二级缓存的范围比它大,Mybatis中对Executor做了个装饰,使用CashingExecutor来维护,也就是如果使用二级缓存,则在创建Executor对象时会对Executor进行装饰,查询时判断CashingExecutor中是否有缓存结果,有就从该缓存中返回,没有就委派给真正的执行器执行查询,比如说SimpleExecutor,再到该执行器中的一级缓存中查。
注意:全局配置文件中二级缓存的总开关默认开启,如果一个Mapper要使用二级缓存,还要单独打开它自己的开关。
<!-- mapper中声明该namespace使用二级缓存-->
<cache type = "org.apache.ibatis.cache.impl.PerpetualCache" size = "1000" eviction = "LRU" flushInterval = "12000" readOnly = "false"/>
<!-- size表示最多缓存的对象个数,eviction表示回收策略,flushInterval表示自动刷新时间-->
select
首先来看一个简单的使用:
- select emp_id as id name mobile note from t_emp where id = #{id} ```
- 简单说一下各个属性的含义:namespace为接口的全限定名,id为接口中对应的方法,parameterType为传入参数的类型,resultType为结果映射的类型…
- 多个参数的传递方式:使用map,使用注解,使用javabean。
使用map:代码可读性降低,用的比较少
List<Employee> selectByMap(Map<String,Object> map);
- select emp_id as id name mobile note from t_emp where name like concat('%',#{name},"%") and mobile like concat('%',#{lastNumber},'%') ```
使用注解:一般参数少于5个时,简单使用,可读性高,参数过多调用起来不方便,显得繁琐复杂
```java
List selectByAnnotation(@Param(“name”)String name,@Param(“lastNumber”)int lastNumber);
``````xml
<select id = "selectByMap" resultType = "emploee">
select emp_id as id name mobile note from t_emp
where name like concat('%',#{name},"%")
and mobile like concat('%',#{lastNumber},'%')
</select>
```
使用javabean:参数多于5个时使用比较方便
```java
public class EmploeeParam{
private String name;
private int lastNumber;
...}
``````xml
- select emp_id as id name mobile note from t_emp where name like concat('%',#{name},"%") and mobile like concat('%',#{lastNumber},'%') ```
- 分页参数RowBounds
RowBounds是Mybatis内置的分页类,有两个参数,offset表示从哪一行开始,limit表示限制条数,在接口方法上加上这个参数直接使用,比如:
List<Employee> selectByAnnotation(@Param("name")String name,@Param("lastNumber")int lastNumber,RowBounds rb);
@Autowired
private EmployeeService service;
@RequestMapping("/emps_like")
@ResponseBody
public ModelAndView getEmps(@RequestParam("name")String name,@RequstParam("lastNumber")int lastNumber) {
ModelAndView mv = new ModelAndView();
RowBounds rb = new RowBounds(0,10);
List<Employee> list = service.selectByAnnotation(name,lastNumber,rb);
mv.put("emps",list)
return mv;
}
```
3. #### insert
- 简单的使用
```xml
<mapper namespace = "com.xxx.dao.EmployeeMapper">
<insert id = "insertEmp" parameterType = "employee">
insert into t_emp(emp_id,name, sex,mobile, note)
values(#{id},#{name},#{sex},#{mobile},#{note})
</insert>
</mapper>
```
- 主键回填
无论支持还是不支持主键自增的数据库都可以使用selectKey来实现主键回填,比如:
```xml
<!--mysql 注意order要设置为after,获取递增主键值用的是函数LAST_INSERT_ID()-->
<insert id="insertUser" parameterType="user" >
<selectKey keyProperty="id" order="after " resultType="int" >
select LAST_INSERT_ID()
</selectKey>
insert into t_user(t_name,sex) values (#{name},#{sex})
</insert>
<!--oracle 注意order要设置为before,因为要先从序列中获取值,然后将值作为主键插入到数据库中-->
<insert id="insertUser" parameterType="user" >
<selectKey keyProperty="id" order="before " resultType="int" >
select t_id.nextval as id from dual
</selectKey>
insert into t_user(t_id,t_name,sex) values (#{t_id},#{name},#{sex})
</insert>
```
对于支持自增主键的数据库MySQL,也可以使用下面这种方式实现主键回填,设置useGeneratedKeys属性为true,keyProperty设置生成主键映射到那个属性上,keyColum是映射到那个字段上。
```java
void insertUser(User user);
```
```xml
<insert id="insertUser" parameterType="user" useGeneratedKeys="true" keyProperty="id">
insert into t_user(t_name,sex) values (#{name},#{sex})
</insert>
```
对于不支持主键自增的数据库Oracle,就通过通用方式selectKey属性和标签来完成。
这里对selectKey标签中的属性简单说明一下:
| 属性 | 描述 |
| ------------- | ------------------------------------------------------------ |
| keyProperty | selectKey语句结果应该被设置的目标属性 |
| resultType | 结果的类型 |
| keyColumn | 匹配属性的返回结果集中的列名称 |
| order | 如果为before,那么它会先执行selectKey语句设置keyProperty然后执行插入语句,如果为after则相反 |
| statementType | 使用哪种语句类型,默认为PREPARED。 |
4. #### update|delete
这两个比较简单,对于批量更新和删除见后面的动态sql一节。
5. #### resultMap
resultMap主要用途有两个:第一个就是当数据库字段名和Java对象属性名不一致是使用,第二个就是用于高级查询,也就是一对多,多对多。代码示例:
```xml
<resultMap id="employee" type="employee">
<id column="id" property="id"/> <!-- 主键列-->
<result column="real_name" property="realName"/>
<result column="sex" property="sex"/>
<result column="birthday" property="birthday"/>
<result column="mobile" property="mobile"/>
<result column="email" property="email"/>
<result column="POSITION" property="position"/>
<result column="note" property="note"/>
<!-- association完成一对一的映射-->
<association property="workCard" column="id" select="com.dao.WorkCardDao.getWorkCardByEmpId"/>
<!-- collection完成一对多的映射-->
<collection property="employeeTaskList" column="id" fetchType="lazy" ofType="employeeTask" select="com.dao.EmployeeTaskDao.getEmployeeTaskByEmpId"/>
<!--鉴别器-->
<discriminator javaType="long" column="sex">
<case value="1" resultMap="maleHealthFormMapper"/>
<case value="0" resultMap="femaleHealthFormMapper"/>
</discriminator>
</resultMap>
<resultMap id="maleHealthFormMapper" type="maleEmployee" extends="employee">
<association property="maleHealthForm" column="id" select="com.dao.MaleHealthFormDao.getMaleHealthForm"/>
</resultMap>
<resultMap id="femaleHealthFormMapper" type="femaleEmployee" extends="employee">
<association property="femaleHealthForm" column="id" select="com.dao.FemaleHealthFormDao.getFemaleHealthForm"/>
</resultMap>
<select id="getEmployee" parameterType="long" resultMap="employee">
select id,real_name as realName,sex,birthday,moblie,email,POSITION,note from t_emploee
where id = #{id}
</select>
sql片段
通常是把一些反复使用的SQL片段比如列名,定义在sql标签中,在其他标签中就可以通过引入的方式使用,比如:
<sql id = "empCols">
id,real_name,sex,birthday,moblie,email,POSITION,note
</sql>
<select id="getEmployee" parameterType="long" resultMap="employee">
select
<include refid = "empCols"/> <!--引入-->
from t_emploee
where id = #{id}
</select>
另外sql标签还支持变量传递,比如:
<sql id = "empCols"> ${alias}.id,${alias}.real_name,${alias}.sex,${alias}.birthday,${alias}.moblie,${alias}.email,${alias}.POSITION,${alias}.note
</sql>
<!--定义的alias变量的值就是表的别名-->
<select id="getEmployee" parameterType="long" resultMap="employee">
select
<include refid = "empCols">
<property name = "alias" value = "e"></property>
</include>
from t_emploee e
where id = #{id}
</select>
级联查询
Mybatis中的级联分三种:鉴别器、一对一和一对多,分别对应着resultMap中的:discriminator、association和collection属性,
- 嵌套查询:会多执行一些SQL语句导致数据库资源的损耗和系统性能的下降,也就是N+1问题,解决方案就是打开延迟加载开关:
<resultMap id="employee" resultType = "employee">
<association property="workCard" column="id"select="com.xxx.dao.WorkCardDao.getWorkCardByEmpId"/>
<collection property="employeeTaskList" column="id" fetchType="lazy" select="com.xxx.dao.EmployeeTaskDao.getEmployeeTaskByEmpId"/>
</resultMap>
<!--就是在查询employee表的时候通过select属性值(statementId)定位到另外的一个查询上,进行了第二次查询,看上去只有一个查询雇员信息的操作,其实后台分别执行了对雇员表、工号表、雇员任务表的查询 -->
<!--解决方案:
1.全局性配置:lazyLoadingEnabled是否开启延迟加载,aggressiveLazyLoading是否采用层级加载。
2.语句配置:fetchType属性可以处理全局定义无法处理的问题,进行自定义。注意该选项只在级联元素association和collection中存在,fetchType=eager 获得当前POJO后立即加载对应的数据,fetchType=lazy 获得当前POJO后延迟加载对应的数据
注意:如果配置了fetchType属性,那么它会忽略上述的两个全局配置项。
-->
- 嵌套结果查询:基于表连接实现级联,会导致SQL变复杂,所需要的配置也比较复杂,一次性取出所有数据会造成内存浪费,给日后的维护工作带来不便,一般用于比较简单,关联不多的场景下。
<resultMap id="employee2" type="employee">
<id column="id" property="id"/>
<result column="real_name" property="realName"/>
<result column="sex" property="sex"/>
<result column="brithday" property="birthday"/>
<result column="mobile" property="mobile"/>
<result column="email" property="email"/>
<result column="position" property="position"/>
<association property="workCard" javaType="workCard" column="id">
<id column="wc_id" property="id"/>
<result column="id" property="empId"/>
<result column="wc_real_name" property="realName"/>
<result column="wc_department" property="department"/>
<result column="wc_mobile" property="mobile"/>
<result column="wc_position" property="position"/>
<result column="wc_note" property="note"/>
</association>
<collection property="employeeTaskList" ofType="employeeTask" column="id" >
<id column="et_id" property="id"/>
<result column="id" property="empId"/>
<result column="et_task_name" property="taskName"/>
<result column="et_note" property="note"/>
<association property="task" javaType="task" column="et_task_id">
<id column="t_id" property="id"/>
<result column="t_title" property="task.title"/>
<result column="t_context" property="task.context"/>
<result column="t_note" property="note"/>
</association>
</collection>
<discriminator javaType="int" column="sex">
<case value="1" resultMap="maleEmployeeMapper2"/>
<case value="0" resultMap="femaleEmployeeMapper2"/>
</discriminator>
</resultMap>
动态SQL
- 概念:动态Sql可以让我们在xml映射文件中以标签的形式编写动态sql,完成逻辑判断和动态拼接Sql的功能。
- 九中动态Sql标签:if|where|set|foreach|choose|when|otherwise|trim|bind
具体使用方法后面文章在写 - 执行原理:使用OGNL从Sql参数对象中计算表达式的值,根据表达式的值动态拼接Sql。
```
动态SQL
- 概念:动态Sql可以让我们在xml映射文件中以标签的形式编写动态sql,完成逻辑判断和动态拼接Sql的功能。
- 九中动态Sql标签:if|where|set|foreach|choose|when|otherwise|trim|bind
具体使用方法后面文章在写 - 执行原理:使用OGNL从Sql参数对象中计算表达式的值,根据表达式的值动态拼接Sql。