Mybatis 是一个类似于Hibernate 的ORM持久化框架,支持普通的SQL查询,存储过程以及高级映射,由于Mybatis 是基于JDBC 做了简单的映射包装,从性能角度来看:
JDBC > Mybatis > Hibernate
有用的链接
一文上手Mybatis:
https://www.cnblogs.com/diffx/p/10611082.html#label2
Mybatis 入门
JDBC 缺点分析
Mybatis 整体结构
Mybatis 使用步骤总结
- 配置mybatis-config.xml 全局配置文件,包括数据源和外部的mapper
- 创建SqlSessionFactory
- 通过SqlSessionFactory 创建SqlSession 对象
- 通过SqlSession 创建数据库CRUD
- 调用session.commit() 提交事务
- 调用session.close() 关闭会话
解决数据库字段和POJO字段不一致的问题
数据库的字段和POJO中的属性名称不一致,导致mybatis无法填充对应信息,修改方法,在sql 语句中使用alias别名: ```xml
<a name="FRHQ8"></a>
## 动态代理实现Mapper类
<a name="D4BU2"></a>
### 传统CRUD 出现的问题
1. 需要创建接口,实现类,mapper.xml
1. 实现类中,使用mybatis 的方法和xml中类似且冗余
1. xml的sql statement 硬编码到java中
思考:是否能只写接口,不写实现类,直接用接口的和Mapper.xml 即可<br />因为在dao(mapper)的实现类中对Sqlsession的使用方式很类似,因此mybatis 提供了接口的动态代理
<a name="dXxos"></a>
### mapper.xml 中配置接口的全路径
mapper.xml + namesapce :<br />如果要使用mybatis的动态代理接口,就需要namespace的值,和需要对应的mapper(dao) 接口的全路径一致,方便mybatis registry mapper。 如下:
```xml
<mapper namespace="com.zpc.mybatis.dao.UserDao">
注意:当sql里出现多个参数的时候,java代码里的参数使用@param 修饰。
注意,即使使用动态代理,修改数据后也要写SqlSession.commit.
动态代理总结
使用SqlSession.getMapper(Mapper.class) 接口不用写接口实现类也可以实现CRUD操作,但是需要具备以下几个条件。
- Mapper 的namespace 必须和mapper的全路径一致
- Mapper 接口的方法名必须和sql定义的id一致
- Mapper 接口中方法的输入参数类型必须和sql定义的parameter type 一致。
- Mapper 接口中方法的输出参数类型必须和sql 定义的resultType一致
XML 映射文件配置
Properties 属性读取外部资源
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
</properties>
properties 内部的属性可以通过读取外部文件来配置。然后其属性就可以在XML配置文件中用来替代 property 属性值。
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
如果属性在不止一个地方被读取,那么MyBatis 将按照下面的方式来加载:
- properties 内部的属性首先被读取。
- 根据properties 里的resource 属性找到配置文件,读取配置文件的属性并覆盖已经读取的同名属性。
- 最后读取作为方法参数读取的属性,并覆盖已经读取的同名属性。
优先级:方法参数传入属性 > recourse 指定配置文件里的属性 > properties 内部配置的属性。
settings 设置
开启驼峰匹配:java 中多个词组成的名字遵循驼峰命名法,数据库多个词组成的字段由下划线分割,驼峰匹配将传统数据库命名转换为java pojo字段。
例子如下:
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
typeAlias
在mapper 文件中需要配置,resultType,parameterType 需要类的完全限定名,即包含类的路径。
<typeAliases>
<typeAlias type="com.zpc.mybatis.pojo.User" alias="User"/>
</typeAliases>
Mybatis 提供了包扫描的功能,扫描之后的别名就是类名。
Mybatis 为普通的java内部类建立了别名,且大小写不敏感
TypeHandlers 类型处理器
无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。可以重写类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。
Plugins 插件拦截器
Mybatis 允许映射语句执行到某一个点的时候进行拦截调用。默认情况下,Mybatis 允许使用插件拦截打方法包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
现在一些MyBatis 插件比如PageHelper都是基于这个原理,有时为了监控sql执行效率,也可以使用插件机制原理:
environments 环境
MyBatis 可以配置成适应多种环境,例如,开发、测试和生产环境需要有不同的配置;
尽管可以配置多个环境,每个 SqlSessionFactory 实例只能选择其一。
虽然,这种方式也可以做到很方便的分离多个环境,但是实际使用场景下,我们更多的是选择使用spring来管理数据源,来做到环境的分离。
mappers
定义 Sql 映射文件的位置
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
Mapper XML 文件详解
CRUD 标签
select
- id 属性Mapper namespace下的statement 唯一标识,和mapper 接口中方法的名字要保持一致。
- resultType 映射为java对象的类型,必须和resultMap 二选一。
-
insert
id 和 parameterType
- userGeneratedKeys: 开启主键回写,插入数据后会填充java对象里的主键属性。
- KeyColumn:指定数据库的主键
-
update
-
delete
id, parameterType, sql 语句
Mybatis 中引入mapper的三种方式
https://juejin.cn/post/6860758368840056840
$ 和 # 的区别
https://blog.csdn.net/zymx14/article/details/78067452
#{} 只是替换?相当于PreparedStatement使用占位符去替换参数,可以防止sql注入。
${} 是进行字符串拼接,相当于sql语句中的Statement,使用字符串去拼接sql;$可以是sql中的任一部分传入到Statement中,不能防止sql注入。
注:
通常在方法的参数列表上加一个注释@Param(“xxxx”) 显式指定参数的名字,然后通过${“xxxx”}或#{“xxxx”}
sql语句动态生成的时候,使用${};
sql语句中某个参数进行占位的时候#{}
ResultMap 使用
注:resultMap 和 resultType必须二选一
链接:关于 resultMap 的理解
https://blog.csdn.net/u012843873/article/details/80198185
使用 select 指定resultMap:
Sql 片段
<sql id=””></sql>
<include refId=”” />
先定义Sql 片段,然后在 select 语句中使用它:
<sql id="commonSql">
id,
user_name,
password,
name,
age,
sex,
birthday,
created,
updated
</sql>
<select id="queryUserById" resultMap="userResultMap">
select <include refid="commonSql"></include> from tb_user where id = #{id}
</select>
<select id="queryUsersLikeUserName" resultType="User">
select <include refid="commonSql"></include> from tb_user where user_name like "%"#{userName}"%"
</select>
动态Sql
if
场景:查询男性用户,如果输入姓名就按姓名模糊查询。
<select id="queryUserList" resultType="com.zpc.mybatis.pojo.User">
select * from tb_user WHERE sex=1
<if test="name!=null and name.trim()!=''">
and name like '%${name}%'
</if>
</select>
choose when otherwise
作用
- 一旦有条件成立的when,则后面的when都不会执行。
- 当所有的when都不执行时,才会执行后面的otherwise。
<select id="queryUserListByNameOrAge" resultType="com.zpc.mybatis.pojo.User"> select * from tb_user WHERE sex=1 <!-- 1.一旦有条件成立的when,后续的when则不会执行 2.当所有的when都不执行时,才会执行otherwise --> <choose> <when test="name!=null and name.trim()!=''"> and name like '%${name}%' </when> <when test="age!=null"> and age = #{age} </when> <otherwise> and name='鹏程' </otherwise> </choose> </select>
where 和 set
```xml
update and set <br />trim tag<br />prefix :在sql语句前加上前缀<br />suffix :在sql语句后加上后缀<br />suffixOverrides: 取消多余的后缀
```xml
<update id="updateUser" parameterType="com.zpc.mybatis.pojo.User">
UPDATE tb_user
<trim prefix="set" suffixOverrides=",">
<if test="userName!=null">user_name = #{userName},</if>
<if test="password!=null">password = #{password},</if>
<if test="name!=null">name = #{name},</if>
<if test="age!=null">age = #{age},</if>
<if test="sex!=null">sex = #{sex},</if>
<if test="birthday!=null">birthday = #{birthday},</if>
updated = now(),
</trim>
WHERE
(id = #{id});
</update>
foreach
collection item saparator open close
<select id="queryUserListByIds" resultType="com.zpc.mybatis.pojo.User">
select * from tb_user where id in
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
缓存
一级缓存
MyBatis 的一级缓存作用域是session,当openSession()后,如果执行相同的Sql, MyBatis 会先命中缓存然后再返回结果
Mybatis 中,一级缓存是默认开启的,并且一致无法关闭。一级缓存满足以下几个条件:
- 同一个session中。
- 相同的SQL 和参数。
- SqlSession.clearCache() 可以强制清空缓存
注: 执行update,delete,insert 的时候会清空缓存
@Test
public void testQueryUserById() {
System.out.println(this.userMapper.queryUserById("1"));
//sqlSession.clearCache();
User user=new User();
user.setName("美女");
user.setId("1");
userMapper.updateUser(user);
System.out.println(this.userMapper.queryUserById("1"));
}
2018-07-01 17:18:15,128 [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Opening JDBC Connection
2018-07-01 17:18:15,399 [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Created connection 242355057.
2018-07-01 17:18:15,401 [main] [com.zpc.mybatis.dao.UserMapper.queryUserById]-[DEBUG] ==> Preparing: select * from tb_user where id = ?
2018-07-01 17:18:15,466 [main] [com.zpc.mybatis.dao.UserMapper.queryUserById]-[DEBUG] ==> Parameters: 1(String)
2018-07-01 17:18:15,492 [main] [com.zpc.mybatis.dao.UserMapper.queryUserById]-[DEBUG] <== Total: 1
User{id='1', userName='bigGod222', password='123456', name='鹏程', age=20, sex=1, birthday='2018-07-01', created='2018-07-01 13:35:40.0', updated='2018-07-01 13:35:40.0'}
2018-07-01 17:18:15,527 [main] [com.zpc.mybatis.dao.UserMapper.updateUser]-[DEBUG] ==> Preparing: UPDATE tb_user set name = ?, updated = now() WHERE (id = ?);
2018-07-01 17:18:15,529 [main] [com.zpc.mybatis.dao.UserMapper.updateUser]-[DEBUG] ==> Parameters: 美女(String), 1(String)
2018-07-01 17:18:15,532 [main] [com.zpc.mybatis.dao.UserMapper.updateUser]-[DEBUG] <== Updates: 1
2018-07-01 17:18:15,532 [main] [com.zpc.mybatis.dao.UserMapper.queryUserById]-[DEBUG] ==> Preparing: select * from tb_user where id = ?
2018-07-01 17:18:15,533 [main] [com.zpc.mybatis.dao.UserMapper.queryUserById]-[DEBUG] ==> Parameters: 1(String)
2018-07-01 17:18:15,538 [main] [com.zpc.mybatis.dao.UserMapper.queryUserById]-[DEBUG] <== Total: 1
User{id='1', userName='bigGod222', password='123456', name='美女', age=20, sex=1, birthday='2018-07-01', created='2018-07-01 13:35:40.0', updated='2018-07-01 17:18:15.0'}
二级缓存
二级缓存的作用域是namespace, 同一个namespace中查询sql可以命中缓存
开启二级缓存
<mapper namespace="com.zpc.mybatis.dao.UserMapper">
<cache/>
</mapper>
注意: 开启二级缓存,必须序列化
public class User implements Serializable{
private static final long serialVersionUID = -3330851033429007657L;
缓存命中:
2018-07-01 17:23:39,335 [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Opening JDBC Connection
2018-07-01 17:23:39,664 [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Created connection 2092769598.
2018-07-01 17:23:39,665 [main] [com.zpc.mybatis.dao.UserMapper.queryUserById]-[DEBUG] ==> Preparing: select * from tb_user where id = ?
2018-07-01 17:23:39,712 [main] [com.zpc.mybatis.dao.UserMapper.queryUserById]-[DEBUG] ==> Parameters: 1(String)
2018-07-01 17:23:39,734 [main] [com.zpc.mybatis.dao.UserMapper.queryUserById]-[DEBUG] <== Total: 1
User{id='1', userName='bigGod222', password='123456', name='美女', age=20, sex=1, birthday='2018-07-01', created='2018-07-01 13:35:40.0', updated='2018-07-01 17:18:15.0'}
2018-07-01 17:23:39,743 [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@7cbd213e]
2018-07-01 17:23:39,744 [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Returned connection 2092769598 to pool.
2018-07-01 17:23:39,746 [main] [com.zpc.mybatis.dao.UserMapper]-[DEBUG] Cache Hit Ratio [com.zpc.mybatis.dao.UserMapper]: 0.5
User{id='1', userName='bigGod222', password='123456', name='美女', age=20, sex=1, birthday='2018-07-01', created='2018-07-01 13:35:40.0', updated='2018-07-01 17:18:15.0'}
在mybatis config 中开启或者关闭缓存:
<settings>
<!--开启驼峰匹配-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!--开启二级缓存,全局总开关,这里关闭,mapper中开启了也没用-->
<setting name="cacheEnabled" value="false"/>
</settings>
配置二级缓存:
eviction: 缓存淘汰策略
flushInterval: 刷新间隔
size: 缓存大小
readOnly: 默认false,可读写的缓存会返回缓存对象的拷贝(通过序列化),会慢一些但是更安全。所以Sql 返回的pojo对象要求开启序列化
高级查询
https://www.cnblogs.com/diffx/p/10611082.html#label15
resultMap 的继承
延迟加载
cacheEnabled: 二级缓存的全局开关,在mybatis-conf.xml 的setting中配置。
一级缓存默认开启
lazyLoadingEnbaled: 开启时,所有关联对象都会延迟加载。只有调用关联对象的时候,关联对象才会加载。关闭时,所有关联对象都会立马加载。
aggressiveLazyLoading: 开启时,当访问一个对象的懒对象属性时,这个对象的其他懒属性也会一并被加载而不用等待程序调用才加载。
userGeneratedKeys: 开启主键回写,useGeneratedKeys = true, keyProperty = “主键对应的Java pojo对象属性名”。 当主键是自增的情况下时,插入记录是不能使用主键的,开启后可获得主键对应的属性值。
延迟加载的基本原理
Mybatis 仅支持association 关联对象和collection 关联对象的懒加载,association即一对一, collection 指一对多。使用lazyLoadingEnabled 决定是否使用懒加载。
它的原理是,使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦 截器方法,比如调用 a.getB().getName(),拦截器 invoke()方法发现 a.getB()是 null 值,那么就会单独发送事先保存好的查询关联 B 对象的 sql,把 B 查询上来, 然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName() 方法的调用。这就是延迟加载的基本原理。
当然了,不光是 Mybatis,几乎所有的包括 Hibernate,支持延迟加载的原理都 是一样的。
Sql 中出现<的解决方案
Spring集成Mybatis
需要用到的依赖
<!--数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.8</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.1.3.RELEASE</version>
</dependency>
<!--spring集成Junit测试-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.1.3.RELEASE</version>
<scope>test</scope>
</dependency>
<!--spring容器-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.3.RELEASE</version>
</dependency>
配置application-context.xml 即应用上下文:
定义beans 骨架
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
申明db.properties 文件,包含db配置信息
jdbc.driver=com.mysql.jdbc.Driver jdbc.host=localhost jdbc.database=ssmdemo jdbc.userName=root jdbc.passWord=123 jdbc.initialSize=0 jdbc.maxActive=20 jdbc.maxIdle=20 jdbc.minIdle=1 jdbc.maxWait=1000
加载配置文件, location = “db properties 所在的文件”
<!-- 加载配置文件 --> <context:property-placeholder location="classpath:properties/*.properties"/>
配置Druid 数据库连接池。
<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url"
value="jdbc:mysql://${jdbc.host}:3306/${jdbc.database}?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull"/>
<property name="username" value="${jdbc.userName}"/>
<property name="password" value="${jdbc.passWord}"/>
<!-- 初始化连接大小 -->
<property name="initialSize" value="${jdbc.initialSize}"></property>
<!-- 连接池最大数据库连接数 0 为没有限制 -->
<property name="maxActive" value="${jdbc.maxActive}"></property>
<!-- 连接池最大的空闲连接数,这里取值为20,表示即使没有数据库连接时依然可以保持20空闲的连接,而不被清除,随时处于待命状态 0 为没有限制 -->
<property name="maxIdle" value="${jdbc.maxIdle}"></property>
<!-- 连接池最小空闲 -->
<property name="minIdle" value="${jdbc.minIdle}"></property>
<!--最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制-->
<property name="maxWait" value="${jdbc.maxWait}"></property>
</bean>
- Spring 和 Mybatis 的结合, datasource 是上文数据库连接池的bean ref。
注意指定mapper 文件的路径
<!-- spring和MyBatis完美整合 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- 自动扫描mapping.xml文件 -->
<property name="mapperLocations" value="classpath:mappers/*.xml"></property>
<!--如果mybatis-config.xml没有特殊配置也可以不需要下面的配置-->
<!-- <property name="configLocation" value="classpath:mybatis-config.xml"/>-->
</bean>
指定Mapper 接口所在的包名,Spring 会自动查找其下的包类并将其注册为Bean。指定SqlSessionFactoryBeanName 为 SqlSessionFactory
<!-- DAO接口所在包名,Spring会自动查找其下的类 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="dao"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property> </bean>
申明事务管理器
<!-- (事务管理)transaction manager --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
用注解方式或者new 一个 application context 来加载配置文件并构建Spring 容器,使用Autowire 注入Mapper 对象