1. HelloWorld
导入mybatis
org.mybatis.spring.boot mybatis-spring-boot-starter 1.3.2
1.1 配置文件法
从XML中构建SqlSessionFactory
mapper
JavaBean
public class Employee { private Integer id; private String lastName; private String email; private String gender; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } @Override public String toString() { return "Employee{" + "id=" + id + ", lastName='" + lastName + '\'' + ", email='" + email + '\'' + ", gender='" + gender + '\'' + '}'; }}
单元测试
/** * 1. 根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象 * 有数据源一些运行环境信息 * 2. sql映射文件,配置了每一个sql,以及sql的封装规则等 * 3. 将sql映射文件注册在全局配置文件中 * 4. 写代码 * 1) 根据全局配置文件得到SqlSessionFactory * 2) 通过SqlSession工厂获取到SqlSession,使用SqlSession执行增删改查,一个SqlSession就是代表和数据库的一次会话,用完关闭 * 3) 使用sql的唯一标识(id)来告诉mybatis执行哪个sql,sql全部保存在sql映射文件(mapper)中 * @throws Exception */ @Test public void test() throws Exception{ String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //2. 获取SqlSession实例,能直接执行已经映射的sql语句 SqlSession sqlSession = sqlSessionFactory.openSession(); /** * Retrieve a single row mapped from the statement key and parameter. * @paramthe returned object type * @param statement Unique identifier matching the statement to use. 传入唯一标识 * @param parameter A parameter object to pass to the statement. 传入参数 * @return Mapped object */ Employee employee = sqlSession.selectOne("com.meituan.mybatis.bean.EmployeeMapper.selectEmp", 1); System.out.println(employee); }
驼峰命名法问题:
mybatis-config.xml配置文件配置时,要注意节点顺序
... ... ... ... ... ... ... ... ... ...
增加设置
支持驼峰命名法
1.2 接口式编程
配置文件
mapper接口
@Mapperpublic interface EmployeeMapper { public Employee getEmpById(Integer id);}
单元测试
@Test public void test01() throws Exception { //1. 获取SqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(); //2. 获取SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); try { //3. 获取接口的实现类对象 // mybatis会为接口自动创建一个代理对象,代理对象去执行增删改查方法 EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); Employee employee = mapper.getEmpById(1); System.out.println(employee); } finally { sqlSession.close(); } } private SqlSessionFactory getSqlSessionFactory() throws Exception{ String resources = "mybatis-config.xml"; InputStream is = Resources.getResourceAsStream(resources); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); return sqlSessionFactory; }
1.3 总结
- 接口式编程
原生: Dao ====> DaoImpl
mybatis: Mapper ==== > xxMapper.xml
- SqlSession代表和数据库的一次,用完必须关闭
- SqlSession和connection一样,都是非线程安全的,每次使用都应该去获取新的对象,不能写为成员变量。
- mapper接口没有实现类,但是mybatis会为这个接口生成一个代理对象
- 两个重要的配置文件
全局配置文件:mybatis-config.xml 包含数据库连接池信息,事务管理器信息等系统运行环境
sql映射文件:保存了每一个sql语句的映射信息
2. Mybatis全局配置文件
3. Mybatis映射文件
MyBatis 的真正强大在于它的映射语句,也是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 就是针对 SQL 构建的,并且比普通的方法做的更好。
3.1 获取自增主键
INSERT INTO employee (last_name, email, gender) VALUES (#{lastName}, #{email}, #{gender}) UPDATE employee SET last_name = #{lastName}, email = #{email}, gender = #{gender} WHERE id = #{id} DELETE FROM employee WHERE id=#{id}
单元测试
/** * 1. mybatis允许增删改直接定义以下类型返回值 * Integer Long Boolean * 2. 手动提交数据 * @throws Exception */ @Test public void test02() throws Exception { SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(); //1. 获取到的SqlSession不会自动提交 SqlSession sqlSession = sqlSessionFactory.openSession(); try { EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); Employee employee = new Employee(null, "jerry", "jerry@tom.com", "2"); System.out.println(employee); System.out.println("============"); mapper.addEmp(employee); System.out.println(employee); // employee.setLastName("jason");// employee.setId(3);// mapper.updateEmp(employee);// mapper.deleteEmpById(3); sqlSession.commit(); } finally { sqlSession.close(); } } private SqlSessionFactory getSqlSessionFactory() throws Exception{ String resources = "mybatis-config.xml"; InputStream is = Resources.getResourceAsStream(resources); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); return sqlSessionFactory; }
3.2 参数处理
1)单个参数:mybatis不会做特殊处理
2)多个参数
异常:
org.apache.ibatis.exceptions.PersistenceException: ### Error querying database. Cause: org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [arg1, arg0, param1, param2]### Cause: org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [arg1, arg0, param1, param2]
操作:
方法:public Employee getEmpByIdAndLastName(Integer id ,String lastName);
取值:#{id},#{lastName}
mybatis会特殊处理,多个参数会被封装成一个map
key:param1....paramN
value:传入的参数值
#{}就是从map中获取指定的key值,或者参数的索引也可以
命名参数:
明确指定封装参数值map的key: @Param("id")
POJO:
如果多个参数正好是业务逻辑的数据模型,可以直接传入POJO:
#{属性名}:取出传入的pojo的属性值
Map:
如果多个参数不是业务模型中的数据,没有对应的pojo,不经常使用,为了方便,也可以传入map
如果多个参数不是业务模型中的数据,但是经常使用,推荐写一个TO(Transfer Object) 数据传输对象
3)参数封装扩展思考:
- public Employee getEmp(@Param("id"))Integer id, String lastName);
取值:id==》#{id/param1} lastName===>#{param2}
- public Employee getEmp(Integer id, @Param("e") Employee emp);
取值:id===》#{param1} lastName===》#{param2.LastName/e.lastName}
- 特别注意:如果是Collection(List、Set)类型或者数组
也会特殊处理,也是把传入的list或者数组封装在map中
key:Collection(collection),如果是List还可以使用(list)
数组(array)
public Employee getEmpById(List<Integer> ids);
取值:取出第一个id的值:#{list[0]}
3.3 结合源码,mybatis如何处理参数
ParamNameResolver解析参数封装map
(1) names:(0=id, 1=lastName)
1) 获取每个标注Param注解的参数param值:id,lastName,赋值给name
2)每次解析一个参数给map中保存信息:key是索引值, value是name的值
name的值:
标注了param注解,注解的值
没有标注:
1、全局配置:useActualParamName,name=参数名(要求JDK1.8)
2、name=map.size() 相当于当前元素的索引
names:{0=id, 1=lastName}
public Object getNamedParams(Object[] args) { final int paramCount = names.size(); //1. 参数为null直接返回 if (args == null || paramCount == 0) { return null; //2. 如果只有一个元素并且没有param注解:args[0],单个参数直接返回 } else if (!hasParamAnnotation && paramCount == 1) { return args[names.firstKey()]; //3. 多个元素或者有Param标注 } else { final Mapparam = new ParamMap
3.4 参数值的获取
#{}:可以获取map中的值或者pojo对象属性的值
${}:可以获取map中的值或者pojo对象属性的值
区别:#{}是以预编译的形式,将参数设置到sql语句中,PreparedStatement
${}:取出的值直接拼装在sql语句中,会有安全问题
大多情况下,取参数的值都应该使用#{},在某些情况下,原生jdbc不支持占位符的地方可以使用${}进行取值,
比如分表;按年份分表拆分 select from 2017_salary可以写为 select from ${year}_salary
3.5 #{}取值规则
更丰富的用法
规定参数的一些规则:
javaType、jdbcType、mode(存储过程)、numericScale、resultMap、typeHandler、jdbcTypeName、expression
jdbcType参演需要在某种特定的条件下被设置
在数据为null的时候,有些数据库可能无法识别mybatis对null的默认处理,如oracle,mybatis对所有的null都映射为原生Jdbc的OTHER类型, oracle无法处理,mysql可以处理
1、#{email, jdbcType=OTHER}
2、全局配置文件mybatis-config.xml中:<setting name="jdbcTypeForNull" value="NULL" />
3.6 Select返回List、Map
- 返回List
- 返回Map,key就是列名,值是对应的值
- 多条纪录封装成一个map,Map<Integer, Employee> 键是这条纪录的主键,值是记录封装的JavaBean
@MapKey("id")public MapgetEmpByLastNameLikeReturnMap(String lastName);
3.7 自定义结果映射封装规则
resultMap
元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets
数据提取代码中解放出来, 并在一些情形下允许你做一些 JDBC 不支持的事情。 实际上,在对复杂语句进行联合映射的时候,它很可能可以代替数千行的同等功能的代码。 ResultMap 的设计思想是,简单的语句不需要明确的结果映射,而复杂一点的语句只需要描述它们的关系就行了。
3.8 关联查询
- 第一种resultMap的写法:
- 第二种resultMap的写法
- 使用association进行分步查询
其中association中select的部分为
3.9 延迟加载
mybatis-config.xml
测试
@Test public void test07() throws Exception { SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(); SqlSession sqlSession = sqlSessionFactory.openSession(); try { Employee employee = sqlSession.selectOne("com.meituan.mybatis.mapper.dao.EmployeeMapperPlus.getEmpByIdStep", 1); System.out.println(employee.getLastName()); System.out.println(employee.getDept().getDeptName()); } finally { sqlSession.close(); } }
输出:
在两次输出中出现一段sql查询
14:23:18.093 [main] DEBUG com.meituan.mybatis.mapper.dao.EmployeeMapperPlus.getEmpByIdStep - ==> Preparing: SELECT * FROM employee WHERE id=? 14:23:18.133 [main] DEBUG com.meituan.mybatis.mapper.dao.EmployeeMapperPlus.getEmpByIdStep - ==> Parameters: 1(Integer)14:23:18.227 [main] DEBUG com.meituan.mybatis.mapper.dao.EmployeeMapperPlus.getEmpByIdStep - <== Total: 1tom14:23:18.228 [main] DEBUG com.meituan.mybatis.mapper.dao.DepartmentMapper.getDeptById - ==> Preparing: SELECT* FROM department WHERE id=? 14:23:18.228 [main] DEBUG com.meituan.mybatis.mapper.dao.DepartmentMapper.getDeptById - ==> Parameters: 2(Integer)14:23:18.269 [main] DEBUG com.meituan.mybatis.mapper.dao.DepartmentMapper.getDeptById - <== Total: 1销售部
3.10 collection定义关联集合封装规则
- 嵌套结果集的方式
- 分步方式
-
扩展:
多列传值
封装成map传递
column="{key1=val1, key2=val2}"
fetchType="lazy",表示使用延迟加载
- lazy:延迟加载
- eager:立即加载
3.11 descriminator 鉴别器
有时一个单独的数据库查询也许返回很多不同 (但是希望有些关联) 数据类型的结果集。 鉴别器元素就是被设计来处理这个情况的, 还有包括类的继承层次结构。 鉴别器非常容易理 解,因为它的表现很像 Java 语言中的 switch 语句。
定义鉴别器指定了 column 和 javaType 属性。 列是 MyBatis 查找比较值的地方。 JavaType 是需要被用来保证等价测试的合适类型(尽管字符串在很多情形下都会有用)。比如:
4. 动态sql
MyBatis 的强大特性之一便是它的动态 SQL。 mybatis支持OGNL表达式。
4.1 if where trim
查询时,某些条件缺失(如id),则可能导致sql拼装出现问题
解决方案:
1、WHERE 1=1
WHERE 1=1AND ***
2、mybatis推荐的的方案:采用<where>
标签,将所有的查询条件包括在内,mybatis就会将where标签中拼装的sql,多出来的and或者or去掉;where
只会去掉前面多出来的and或者or
id=#{id} AND last_name LIKE #{lastName} AND email=#{email} AND gender=#{gender}
3、trim标签的使用
4.2 choose set标签
choose 分支选择,相当于带了break的switch-case
set标签用于update,可以去掉多掉多余的“,”
4.3 foreach
批量插入:
INSERT INTO employee (last_name, email, gender, d_id) VALUES (#{emp.lastName}, #{emp.email}, #{emp.gender}, #{emp.dept.id})
注意:list类型的参数会特殊处理封装在map中,map的key就叫list。如果期望此时传入的参数名由自己定制,可以
1、@Param("*")
2、将list传入自己的map中
4.4 mybatis内置参数
mybatis默认有两个内因参数
1、 _parameter:代表整个参数
单个参数:_parameter就是这个参数
多个参数:参数会被封装为一个map;_parameter就是代表这个map
2、 _databaseId: 如果配置了databaseIdProvider标签,
_databaseId就是代表当前数据库的别名
4.5 bind
bind可以将OGNL表达式的值绑定到一个变量中,方便后来引用,例如:
<bind name="_lastName" value="'%'+lastName+'%'">
4.6 sql 抽取可重用的片段
使用sql标签定义可重用片段
employee_id, last_name, email
使用include标签引用可重用片段
5. 缓存机制
缓存可以极大的提升查询效率。mybatis默认定义了两级缓存:
- 默认情况下,只有一级缓存(SqlSession级别缓存,也称为本地缓存)开启。
- 二级缓存需要手动开启和配置,是基于namespace级别的缓存,也称全局缓存。
- 为了提高扩展性,mybatis定义了缓存接口Cache。可以通过实现cache接口来自定义二级缓存
5.1 一级缓存
与数据库同一次会话期间查询到的数据会放在本地缓存中,以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库。mybatis默认开启一级缓存。
一级缓存失效情况
- sqlSession不同
- sqlSession相同,查询条件不同(当前一级缓存中还没有这个数据)
- sqlSession相同,两次查询期间执行了增删改(可能会导致当前数据失效)
- 手动清空了缓存
sqlSession.clearCache();
5.2 二级缓存
基于namespace级别的缓存,一个namespace对应一个二级缓存
工作机制:
1、一个会话(session),查询一条ovry,这个数据就会被放在当前会话的一级缓存中
2、 如果当前会话关闭,一级缓存中的数据会被保存到二级缓存中;新的会话查询信息,可以参照二级缓存
3、 不同namespace查出的数据会放在自己对应的缓存中(map)
4、 查出的数据会默认先放在一级缓存中,只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中
使用:
1、开启全局二级缓存配置,<setting name="cacheEnabled" value="true"/>
2、在某个namespace中进行配置
- eviction
1、LRU – 最近最少使用的:移除最长时间不被使用的对象。
2、FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
3、SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
4、WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
默认LRU
- flushInterval:缓存刷新间隔
缓存多久清空一次,默认不清空,单位毫秒
- readOnly:是否只读
true:只读,mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。mybatis为了加快获取速度,直接会将数据在缓存中的引用交给用户。特点:不安全,速度快
false:不只读。mybatis认为获取的数据可能会被修改,mybatis会利用序列化&反序列的技术克隆一份新的数据给用户。特点:安全,速度慢
- size:缓存多少个元素
- type:指定自定义缓存的全类名,实现Cache接口即可
3、 pojo需要实现序列化接口
5.3 和缓存有关的设置和属性
1、<setting name="cacheEnabled" value="true"/>
开启或关闭缓存
2、 每个select标签都有useCache属性,true为使用,false为不使用(一级缓存依然使用,二级缓存不使用)
3、每个增删改标签的flushCache属性值为true,即增删改执行完成后应付清除缓存,包括一级、二级缓存。查询标签默认flushCache="false",当设置为true时,每次查询之前都会清空缓存,缓存没有被使用
4、sqlSession.clearCache();
只是清除一级缓存
5、localCacheScope:本地缓存作用域(一级缓存),可选择SESSION、STATEMENT。STATEMENT可以禁用缓存
5.4 缓存原理