1. SqlSessionFactoryBuilder负责构建SqlSessionFactory,而且提供了多个build()方法的重载。也就是说:此对象能够从xml配置文件,或从Configuration对象来构建SqlSessionFactory。
2. SqlSessionFactory就是建立SqlSession实例的工厂。经过openSession方法来获取SqlSession对象。并且,SqlSessionFactory一旦被建立,那么在整个应用程序期间都存在。
3. SqlSession是一个面向程序员的接口,它提供了面向数据库执行sql命令所需的全部方法。SqlSession对应一次数据库会话,它是线程不安全的。html
MyBatis开发DAO层有两种方式:java
按照JDBC课程中封装dao层的方式,咱们能够先封装一个 Util 工具类,在此工具类中封装一个获取SqlSessionFactory的方法。而后建立dao接口和实现类。
SqlSessionFactory工具类:程序员
package com.neusoft.util; import java.io.IOException; import java.io.Reader; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; public class Util { public static SqlSessionFactory sqlSessionFactory = null; public static SqlSessionFactory getSqlSessionFactory() { if(sqlSessionFactory==null){ String resource = "mybatis/SqlMapConfig.xml"; try { Reader reader = Resources.getResourceAsReader(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); } catch (IOException e) { e.printStackTrace(); } } return sqlSessionFactory; } }
dao接口:web
package com.neusoft.dao; import java.util.List; import com.neusoft.po.Emp; public interface EmpDao { public Emp getEmpById(int empno); public List<Emp> listEmp(); }
dao的实现类:sql
package com.neusoft.dao.impl; import java.util.List; import org.apache.ibatis.session.SqlSession; import com.neusoft.dao.EmpDao; import com.neusoft.po.Emp; import com.neusoft.util.Util; public class EmpDaoImpl implements EmpDao{ @Override public Emp getEmpById(int empno){ SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); Emp emp = sqlSession.selectOne("emp.getEmpById",empno); sqlSession.close(); return emp; } @Override public List<Emp> listEmp(){ SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); List<Emp> list = sqlSession.selectList("emp.listEmp"); sqlSession.close(); return list; } }
测试:数据库
EmpDao dao = new EmpDaoImpl(); Emp emp = dao.getEmpById(7369); System.out.println(emp); List<Emp> list = dao.listEmp(); for(Emp emp : list) { System.out.println(emp); }
从上面代码中能够发现,使用原始dao方式存在不少问题:apache
只须要mapper接口和mapper.xml映射文件,Mybatis能够自动生成mapper接口实现类代理对象。编mapper接口须要遵循4个一致数组
- Mapper映射文件的名字和mapper接口的名字一致
- Mapper映射文件中statementId的值,与mapper接口中对应的方法名一致
- Mapper映射文件中statement的输入参数parameterType的类型,与mapper接口中对应方法的参数类型一致。
- Mapper映射文件中statement的输出参数resultType的类型,与mapper接口中对应方法的返回值类型一致
解释一下:缓存
第一个一致:
并且前面学习提到过,xml映射文件的namespace属性的取值问题,当使用原始dao开发时,能够随意取值;使用mapper代理开发时,取值为mapper接口的全路径
安全
第二个一致:方法名一致
第三个一致:输入类型一致
第四个一致:输出类型一致
还要记得在SqLMapConfig中注册映射文件
优化:
若是映射文件与mapper接口名称一致,且处在同一个文件夹内,那么就可使用接口来批量加载映射文件。
注意一个是“/”,一个是“.”
在第四个一致中,xml文件中的输出类型写的很长,也能够进行简化,一样在SqlMapConfig中
代码:
<?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代理方式开发时,有特色的取值。即为mapper接口的全路径--> <!-- 1. Mapper映射文件的名字和mapper接口的名字一致--> <!-- 2. Mapper映射文件中statementId的值,与mapper接口中对应的方法名一致--> <!-- 3. Mapper映射文件中statement的输入参数parameterType的类型,与mapper接口中对应方法的参数类型一致。--> <!-- 4. Mapper映射文件中statement的输出参数resultType的类型,与mapper接口中对应方法的返回值类型一致--> <mapper namespace="com.neuedu.mapper.EmpMapper"> <!-- 按id查询员工 --> <select id="findEmpById" parameterType="int" resultType="Employee"> <!-- id就是这条语句的惟一标识,parameterType是员工id的属性,resultType是返回类型,要把实体类的路径写完整 --> select * from tb_emp where id = #{value} <!-- 占位符要使用#{} parameterType的类型若是为 简单类型(基本类型和String),#{}中的值任意。--> </select> <!-- 按名称模糊查询,当查询结果有多个时,resultType的类型为pojo--> <select id="findEmpByName" parameterType="string" resultType="Employee"> <!-- 不使用拼接,要在test中加% --> SELECT * FROM tb_emp WHERE NAME LIKE #{value} <!-- 字符串拼接的方法。注意:慎用,会产生sql注入。--> <!-- SELECT * FROM tb_emp WHERE NAME LIKE '%${value}%'--> </select> <!-- 删除员工 --> <delete id="deleteEmp" parameterType="int"> delete from tb_emp where id=#{value} </delete> <!-- 更新员工--> <!-- 若是输入参数为pojo类型,#{pojo对象的属性名} --> <update id="editEmp" parameterType="com.neuedu.pojo.Employee"> update tb_emp set loginName=#{loginName},name=#{name},email=#{email}, status=#{status},deptId=#{deptId},photoPath=#{photoPath} where id=#{id} </update> <!-- 插入员工 --> <insert id="saveEmp" parameterType="com.neuedu.pojo.Employee"> INSERT INTO tb_emp (loginname,PASSWORD,NAME,hiredate,email,photopath,deptId) VALUES (#{loginName},#{password},#{name},#{hiredate},#{email},#{photoPath},#{deptId}) <!-- order: 执行时机 keyColumn:表中自动名称 keyProperty:映射的pojo属性名称 --> <selectKey order="AFTER" resultType="int" keyColumn="id" keyProperty="id"> SELECT LAST_INSERT_ID() </selectKey> </insert> </mapper>
package com.neuedu.mapper; import java.util.List; import com.neuedu.pojo.Employee; public interface EmpMapper { /** * 登陆方法 * @param loginName 登陆名 * @param password 密码 * @return 登陆员工的信息 */ Employee login(String loginName, String password); /** * 添加员工 * @param emp 插入信息 */ void saveEmp(Employee emp); /** * 删除员工 * @param id 员工id */ void deleteEmp(Integer id); /** * 修改员工 * @param emp 员工信息 */ void updateEmp(Employee emp); /** * 按id查询 * @param id 员工id * @return 员工信息 */ Employee findEmpById(Integer id); /** * 按照name查询 * @param name 员工姓名 * @return 员工列表 */ Employee findEmpByName(String name); }
package com.neuedu.test; import com.neuedu.mapper.EmpMapper; import com.neuedu.pojo.Employee; import com.neuedu.utils.DBUtils; import org.apache.ibatis.session.SqlSession; import org.junit.Test; public class TestMyMapper { @Test public void testFindById() { SqlSession session = DBUtils.getSession(); EmpMapper empMapper = session.getMapper(EmpMapper.class); Employee emp = empMapper.findEmpById(2); System.out.println(emp.getName()); session.close(); } }
parameterType 属性:表示执行sql语句时,须要使用的数据。
在statement语句中#{}
的值能够任意
输入参数为pojo类型,#{}
中的值为pojo对象的属性名
需求查找员工所在的部门名称
问题提出:员工表emp中没有部门名的字段,须要联合到部门表dept;即Employee实体类没法知足需求
解决方法:
在包装POJO类中,关联多个POJO对象,好比按照上例能够把emp和dept联合起来组成EmpDept类;
sql语句
写sql语句时输入类型也能够直接用:
注意#{}里面的值,empdept里没有name属性只有emp与dept,可是能够用emp与dept来访问他们的name
测试
写测试的时候会复杂一点
在接口中加入方法
若是resultType为简单类型,查询结果必须为一个值。一行一列。
四个一致:在接口中定义方法
测试方法
若是select语句使用投影查询时。即查询列表只定义表中部分字段。
此时结果中只会封装查询列表中有的字段,查询列表中没有的字段,输出参数pojo属性值为默认值
Employee中的全部属性
写一个投影查询
能够看出查询语句并无把全部的属性都查出来
接口定义方法:
在Employee.java实体类中加入一个tostring方法,只选取部分数据库中的字段:
测试:
输出结果:
一样能映射成功。
思考:此时用的是表中字段的名字与属性映射仍是用的查询列表的名字与属性映射?
验证:
在查询列表中起别名再测试
结果:
email为空。
说明输出参数进行映射时,是使用的查询列表的别名和pojo属性名进行映射,若是字段别名和pojo属性名不对应,则会映射失败
把全部的都起别名,只剩下id能对应上
结果:
结论:即便只有一个属性能对应,也会建立pojo对象
若是全部的都对应不上呢?
结果:
不报错,输出为空。
结论:全部属性都对应不上则不会建立pojo对象
若是必需要起别名来查询,怎么解决?
能够利用resultMap
在statement语句中把resultType的位置写为resultMap
- resultMap是一个标签,每个statement语句都会根据标签的id去找他的映射类型,上例中的类型就是Employee,有不一样的对应不上的本身定义。
<id>
普通属性映射用<result>
再次测试:
email映射成功
可是resultMap的主要做用是实现关联映射,在后续会学到
运用:条件查询
在前几天j学习aveweb时用servlet写条件查询时,写了一个"1=1",再链接其余的条件
测试
优化:放在<where>
标签里面,<if>
语句会加上and
测试能成功,可是发现sql语句中没有了and
解释:where标签会为sql语句添加where字句,同时会去掉第一个条件前面的and,因此放心的加and,无论哪条语句是第一个条件都会自动去掉前面的and。
把全部条件去掉
测试:
全部条件都不知足where天然也就不起做用了
第二个sql语句:
在接口类中定义:
测试:
结果:
都没什么问题。
重点是两个语句的where标签中的语句是如出一辙的,就是说这段代码能够复用,那就能够封装起来。
把重复代码写在<sql>
标签内
在statement中用<include>
来包含<sql>
语句
注意:
改进:把sql片断的where删除
在<include>
前加上<where>
,
用于批量删除。
输入参数中,应该包含一个数组(List)类型的数据,该数据包含要删除的全部id。
此时输入参数为pojo,即Employee。须要扩展属性,添加一个int[] ids属性
删除语句
测试
结果
对比着理解sql的书写:
参数的含义:
tem表示集合中每个元素进行迭代时的别名
index指定一个名字,用于表示在迭代过程当中,每次迭代到的位置
open表示该语句以什么开始
separator表示在每次进行迭代之间以什么符号做为分隔符
close表示以什么结束
第二条的书写
遇到and与or时,and的优先级更高,因此能够在or的语句外加一个"()"
sql语句中,除了上面用到的几个子标签,还有其余的
choose:多条件判断,按顺序判断其内部when标签中的test条件出否成立,若是有一个成立,则 choose 结束。
当 choose 中全部 when 的条件都不满则时,则执行 otherwise 中的sql。
相似于Java 的 switch 语句,choose 为 switch,when 为 case,otherwise 则为 default。
trim:插入数据,动态拼接insert语句.主要功能是能够在本身包含的内容前加上某些前缀,也能够在其后加上某些后缀,与之对应的属性是prefix和suffix
能够把包含内容的首部某些内容覆盖,即忽略,也能够把尾部的某些内容覆盖,对应的属性是prefixOverrides和suffixOverrides;
帮助咱们去除末尾的“,”并填上“()”。
(当前阶段只是了解)
Mybatis提供了一个项目,能够经过数据库中的表自动导出对应的pojo、mapper.xml、mapper.java。逆向工程只适合单表操做。
如下图的电商业务为例,查询名叫张三的人买过哪些东西,就须要多表查询
数据库表详情:
需求:查询订单关联的用户
测试
结果
没什么问题,可是当业务需求愈来愈多,须要的包装的pojo就会愈来愈多,与数据库无关的实体类愈来愈多,致使系统维护起来很麻烦
准备工做:在pojo的order类中加入user,即定义关联字段并提供get与set
查询语句
resultMap进行映射
- resultMap的type属性即为select语句的输出类型,即为order(进行了打包,完整路径为com.nenedu.pojo.Order)
- 先对order类中自己有的属性进行映射,主键用
<id>
,普通属性用<result>
- 再对order类中加入的User进行映射,一对一映射用
<association>
,其中的JavaType表示关联属性的类型,即为User(com.neuedu.pojo.User)- 细心,一个属性一个属性的来。
接口方法
测试
结果与resultTyoe相同。
注意其结构类型:
需求:查询用户及其关联的全部订单
要在User类中关联订单order的类型,订单有不少,采用list
查询语句
resultMap进行映射
- 先映射User自己的属性,再映射关联属性
- 一对多的关联属性用collection,使用ofType属性,他表示集合中存放的对象的类型(泛型属性)
- JavaType表示的是关联属性的类型,但在user中关联的是list,咱们须要的是list内部的对象的属性
接口方法:
测试:
结果:
需求:查询刘备的信息及其购买过的商品信息
sql语句:
关联了4个表,主表为User
在以前例子的基础上再往order表中关联订单明细OrderDetail,一个订单有多个明细用list;订单明细表OrderDetail中关联商品Item,一个明细只针对一个商品.
关联环为:User->Order->OrderDetail->Item
查询语句
接口方法:
叫刘备的可能有不少,用list装起来。
resultMap进行映射
<resultMap type="user" id="findUserAndItemMap"> <!-- user表 --> <id column="uid" property="id"/> <result column="uname" property="name"/> <!-- 一对多 <list>order表 --> <collection property="orderList" ofType="Order"> <id column="oid" property="id"/> <result column="orderNum" property="orderNum"/> <result column="uid" property="userId"/> <!-- 一对多 <list>orderdetail表 --> <collection property="detailList" ofType="orderDetail"> <id column="oid" property="orderId"/> <id column="iid" property="itemId"/> <result column="count" property="count"/> <!-- 一对一 item表 --> <association property="item" javaType="item"> <id column="iid" property="id"/> <result column="iname" property="name"/> </association> </collection> </collection> </resultMap>
每一个表看清楚映射的单个(association)仍是多个(collection),找好每一个表的属性,一步一步来就不容易错
测试:
结果:
使用resultMap实现关联映射时:
- 使用association标签完成多对一或一对一映射。
a. association标签:将关联查询信息映射到一个po对象中。
b. association标签中的javaType属性:表示该po对象的类型。
c. association标签中的select属性:表示应用哪个关联查询。
d. association标签中的column属性:表示应用关联查询的条件。- 使用collection标签完成一对多,多对多映射。
a. collection标签:将关联查询信息映射到一个list集合中。
b. collection标签的ofType属性:表示该集合中的元素对象的类型。
c. collection标签中的select属性:表示应用哪个关联查询。
d. collection标签中的column属性:表示应用关联查询的条件。
延迟加载:执行查询时,关联查询不会当即加载。只有在使用关联数据时才会加载。
1. 优势:按需加载,提升效率。
2. 具备关联关系的对象才会存在延迟加载
3. 例如:查询订单关联用户。默认当查询订单信息时,会当即加载关联的用户信息。若是程序中只须要使用订单信息,而不须要使用关联的用户信息,则当即加载关联的用户信息就没有必要。能够对订单关联的用户信息实现延迟加载,即便用到用户信息是再加载,不用就不加载,提供系统的执行效率。
4. 在mybatis中,只有association、collection标签具备延迟加载的功能。
注意: 要使用关联查询的延迟加载,就必需要使用单独查询形式。而且,须要先启用MyBatis的延迟加载配置(须要配置两项):
<!-- 若是aggressiveLazyLoading为true,那么lazyLoadingEnabled即便为true也无效。 --> <settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> </settings>
查询语句
在UserMapper.xml
中有语句:
<select id="findUserById" parameterType="int" resultType="user"> select id,name from users where id=#{value} </select>
在OrderMapper.xml
有语句:
<!-- 查询订单关联用户lazy --> <resultMap type="Order" id="findOrderAndUserLazyMap"> <id column="id" property="id"/> <result column="orderNum" property="orderNum"/> <result column="userId" property="userId"/> <!-- 映射关联属性 --> <!-- select 属性 调用已经存在的statement --> <association property="user" select="com.neuedu.mapper.UserMapper.findUserById" column="userId"></association> </resultMap> <select id="findOrderAndUserLazy" parameterType="string" resultMap="findOrderAndUserLazyMap"> SELECT id,orderNum,userId FROM orders WHERE orderNum = #{orderNum } </select>
调用了在另外一个xml文件的查询语句,用到的时候才执行。
进行断点测试:
- 第一条语句执行的时候,user的值为空,第二条语句没有加载执行;
- 执行到session.commit();发送了数据,user中也有值
注释掉一条语句测试:
结果:只发送了一条数据,也只执行了一条语句
可是在配置文件中将lazyLoadingEnabled
的true改成false,即便注释了语句也仍是会加载
确认了mybatis默认是当即加载的。
在上面的开发过程当中咱们用的都是映射文件开发,就是把查询语句写在xml文件当中,而后在接口类中调用。
MyBatis也支持使用注解来配置映射语句。直接把语句写在接口类中。
记得把文件改一下,已经没有Employee.xml文件了。
主要有四种注解来实现增删改查:@Select、@Insert、@Update、@Delete
基础的书写以下
package com.neuedu.mapper; import java.util.List; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Select; import com.neuedu.pojo.Employee; public interface EmpMapper { @Select(value="select * from tb_emp where id=#{value}") Employee findEmpById(Integer id); @Select(value="select * from tb_emp where name like '%${value}%'") List<Employee> findEmpByName(String name); void saveEmp(Employee emp); @Delete(value="delete from tb_emp where id=#{value}") void deleteEmp(Integer id); void editEmp(Employee emp); }
测试类:
@Test public void testFindById() throws Exception{ SqlSession session = sf.openSession(); EmpMapper mapper = session.getMapper(EmpMapper.class); mapper.findEmpById(3); session.commit(); session.close(); }
测试一个:
成功。
其余的如用注解实现动态sql、懒加载等等在这个里面写的很是详细了
参考文档:mybatis注解开发
Mybatis的一级缓存是SqlSession级别的缓存,每一个SqlSession使用独立的一级缓存空间。
当执行Mapper接口中方法时,得到一个sqlSession的对象。sqlSession对象线程不安全
每次操做都会使用独立的sqlSession对象。
当sqlSession对象被建立时,sqlSession对象的内部会开辟一个缓存区域(HashMap)
当经过同一个SqlSession对象进行查询操做时。一级缓存会起做用。
Mybatis默认是开启一级缓存的,并且不能关闭一级缓存。
当一个sqlSession对象发起了一次查询操做时,首先会到一级缓存中查找是否存在该对象
若是在缓存中没有找到对应的对象,则发生sql语句到数据库中进行查询。并把查询到的对象保存到一级缓存中一份
当经过同一个sqlSession对象发起第二次查询操做时,首先会到一级缓存中查找,若是找到对应的对象,则不会在发送sql语句到数据库。这样,能够提升查询效率。
在两次查询的中间,若是执行了commit操做(update、insert、delete),会清除缓存中的全部数据。
测试
@Test public void testFindUserCache() { SqlSession session = sf.openSession(); UserMapper userMapper = session.getMapper(UserMapper.class); //第一次查询 User u1 = userMapper.findUserById(1);//发送sql u1.setName("tom"); userMapper.modifyUser(u1); session.commit();//清空一级缓存 //第二次查询 User u2 = userMapper.findUserById(1);//不会发送,使用缓存中的对象 session.close(); SqlSession session2 = sf.openSession(); UserMapper userMapper2 = session2.getMapper(UserMapper.class); User u3 = userMapper2.findUserById(1);//发送sql,新的sqlSession对象,使用新的一级缓存 session2.close(); }
二级缓存就是,Mapper级别的缓存,当不一样的sqlSession对象访问同一个Mapper接口中的方法时,会使用到二级缓存。
当使用一个sqlSession对象进行查询操做时,到二级缓存中查找,若是没有找到,则发送sql语句到数据库中查询,并把查询结果保存到二级缓存
在两次查询过程当中,若是执行了commit操做,则清空二级缓存。
当使用另一个sqlSession对象进行查询操做时,到二级缓存中查找,若是找到,则不发送sql语句查询数据库。
默认mybatis是开启二级缓存的,但能够在sqlMapConfig.xml文件中配置二级缓存。
要在须要被缓存的xxxMapper.xml文件中配置,该Mapper使用二级缓存。
测试
第一次查询时,到二级缓存中查找是否存在对象
缓存命中率。 在缓存中查找对象的命中率。
第二次查询时,二级缓存中存在对象,则命中率为0.5
使用二级缓存要注意: 一些异常
被二级缓存缓存的对象,有可能会被执行序列化或反序列化操做,因此 被缓存的对象所属的类必须支持序列化, 要实现Sericlizable接口。
需求:分布式系统,系统存在两个子系统,在一个系统中登陆,在另一个系统上是否也可以使用登陆信息?
引入jar包
<dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> <version>2.10.3</version> </dependency> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.1.0</version> </dependency>
假日ehcache 实现类
在系统中加入ehcache的配置文件
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true" monitoring="autodetect" dynamicConfig="true"> <!-- 磁盘的缓存路径 --> <diskStore path="d:\\ehcache\temp"/> <defaultCache maxElementsInMemory="100" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="1200" maxElementsOnDisk="10000000" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> </defaultCache> <!-- Cache配置 · name:Cache的惟一标识 · maxElementsInMemory:内存中最大缓存对象数。 · maxElementsOnDisk:磁盘中最大缓存对象数,如果0表示无穷大。 · eternal:Element是否永久有效,一但设置了,timeout将不起做用。 · overflowToDisk:配置此属性,当内存中Element数量达到maxElementsInMemory时,Ehcache将会Element写到磁盘中。 · timeToIdleSeconds:设置Element在失效前的容许闲置时间。仅当element不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。 · timeToLiveSeconds:设置Element在失效前容许存活时间。最大时间介于建立时间和失效时间之间。仅当element不是永久有效时使用,默认是0.,也就是element存活时间无穷大。 · diskPersistent:是否缓存虚拟机重启期数据。(这个虚拟机是指什么虚拟机一直没看明白是什么,有高人还但愿能指点一二)。 · diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。 · diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每一个Cache都应该有本身的一个缓冲区。 · memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你能够设置为FIFO(先进先出)或是LFU(较少使用)。 --> </ehcache>
应用场景:常常被访问,但不多被修改。适合进行缓存。 电商系统中的商品信息不适合mybatis缓存。