8.分布查询的优点
- 分布查询的优点是可以实现延迟加载
- 延迟加载可以避免在分步查询中执行所有的SQL语句,节省资源,实现按需加载
- 需要在核心配置文件中添加如下的配置信息
<settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> </settings>
- lazyLoadingEnabled表示全局的延迟加载开关,true表示所有关联对象都会延迟加载,false表示关闭
- aggressiveLazyLoading表示是否加载该对象的所有属性,如果开启则任何方法的调用会加载这个对象的所有属性,如果关闭则是按需加载
- 由于这个配置是在核心配置文件中设定的,所以所有的分步查询都会实现延迟加载,而如果某个查询不需要延迟加载,可以在collection标签或者association标签中的fetchType属性设置是否使用延迟加载,属性值lazy表示延迟加载,属性值eager表示立即加载
9.动态SQL
- if标签
- if标签通过test属性给出判断的条件,如果条件成立,则将执行标签内的SQL语句
- 范例
<select id="getEmpByCondition" resultType="Emp"> select * from t_emp where <if test="empName != null and empName != ''"> emp_name = #{empName} </if> <if test="age != null and age != ''"> and age = #{age} </if> <if test="gender != null and gender != ''"> and gender = #{gender} </if> </select>
- where标签
- 考虑if标签中的范例出现的一种情况:当第一个if标签条件不成立而第二个条件成立时,拼接成的SQL语句中where后面连着的是and,会造成SQL语句语法错误,而where标签可以解决这个问题
- 范例
<select id="getEmpByCondition" resultType="Emp"> select * from t_emp <where> <if test="empName != null and empName != ''"> emp_name = #{empName} </if> <if test="age != null and age != ''"> and age = #{age} </if> <if test="gender != null and gender != ''"> and gender = #{gender} </if> </where> </select>
- where标签只会在子标签返回任何内容的情况下才插入WHERE子句。而且,若子句的开头有多余的and或者or,where标签也会将它们去除,但是子句末尾的and或者or不能去除
- trim标签
- trim标签用于去掉或添加标签中的内容
- trim标签常用属性
- prefix:在trim标签中的内容的前面添加某些内容
- prefixOverrides:在trim标签中的内容的前面去掉某些内容
- suffix:在trim标签中的内容的后面添加某些内容
- suffixOverrides:在trim标签中的内容的后面去掉某些内容
- 用trim实现where标签范例相同的功能
<select id="getEmpByCondition" resultType="Emp"> select * from t_emp <trim prefix="where" prefixOverrides="and"> <if test="empName != null and empName != ''"> emp_name = #{empName} </if> <if test="age != null and age != ''"> and age = #{age} </if> <if test="gender != null and gender != ''"> and gender = #{gender} </if> </trim> </select>
- choose、when、otherwise标签
- 这三个标签是组合使用的,用于在多条件中选择一个条件,类似Java中的if...else if...else...语句
- 范例
<select id="getEmpByCondition" resultType="Emp"> select * from t_emp where gender = #{gender} <choose> <when test="empName != null and empName != ''"> and emp_name = #{empName} </when> <when test="age != null and age != ''"> and age = #{age} </when> </choose> </select>
- 当某个when标签的条件满足时将对应的SQL语句返回,如果都不满足并且有otherwise标签时,才会返回otherwise标签中的SQL语句
- foreach标签
- foreach标签允许指定一个集合或数组,并且对这个集合或数组进行遍历
- foreach标签可以用的属性有
- collection:指定需要遍历的集合或数组
- item:当前遍历到的元素
- index:当前遍历到的元素的序号
- 当遍历的集合是Map类型时,index表示键,item表示值
- open:指定遍历开始前添加的字符串
- close:指定遍历开始后添加的字符串
- separator:指定每次遍历之间的分隔符
- collection属性值注意事项
- 如果遍历的是List时,属性值为list
- 如果遍历的是数组时,属性值为array
- 如果遍历的是Map时,属性值可以是map.keys()、map.values()、map.entrySet()
- 除此之外,还可以在映射方法的参数中使用@Param()注解自定义collection属性值
- 批量添加数据
<insert id="addMoreEmp"> insert into t_emp values <foreach collection="list" separator="," item="emp"> (null,#{emp.empName},#{emp.age},#{emp.gender},null) </foreach> </insert>
- 批量删除数据
<delete id="deleteMoreEmp"> delete from t_emp where emp_id in <foreach collection="array" item="empId" separator="," open="(" close=")"> #{empId} </foreach> </delete>
- sql标签
- 用于记录一段通用的SQL语句片段,在需要用到该SQL语句片段的地方中通过include标签将该SQL语句片段插入
- sql标签通过id属性唯一标识一个SQL语句片段,include标签通过refid属性指定使用某个SQL片段
- 范例
<sql id="item"> emp_id,emp_name,age,gender,dept_id </sql> <select id="getEmpByEmpId" resultType="Emp"> select <include refid="item"></include> from t_emp where emp_id = #{empId} </select>
四.核心配置文件
1.文件结构
- 核心配置文件命名建议是mybatis-config.xml,无强制要求
- 核心配置文件主要用于配置连接数据库的环境以及MyBatis的全局配置信息
- 核心配置文件存放的位置是maven工程下的src/main/resources目录下
- 简易结构如下,核心配置文件的标签不止这几个
<?xml version="1.0" encoding="UTF-8" ?> <!--DTD约束--> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 1.environments配置数据库的环境,环境可以有多个 2.default属性指定使用的环境 --> <environments default="development"> <!-- 1.environment配置具体某个数据库的环境 2.id属性唯一标识这个环境 --> <environment id="development"> <!-- 1.transactionManager设置事务管理方式 2.type属性取值有“JDBC|MANAGED” 3.JDBC指当前环境中使用的是JDBC中原生的事务管理方式,事务的提交或回滚需要手动处理 4.MANAGED指被管理,例如Spring中 --> <transactionManager type="JDBC"/> <!-- 1.dataSource配置数据源 2.取值有"POOLED|UNPOOLED|JNDI" 3.POOLED表示使用数据库连接池缓存数据库连接 4.UNPOOLED:表示不使用数据库连接池 5.JNDI表示使用上下文中的数据源 --> <dataSource type="POOLED"> <!--设置链接数据库的驱动--> <property name="driver" value="com.mysql.jdbc.Driver"/> <!--设置连接数据库的地址--> <property name="url" value="jdbc:mysql://localhost:3306/ssm"/> <!--设置连接数据库的用户名--> <property name="username" value="root"/> <!--设置连接数据库的密码--> <property name="password" value="lxq"/> </dataSource> </environment> </environments> <!--mappers用于引入映射的配置文件--> <mappers> <!--mapper用于指定某个映射文件,resource属性指定文件路径--> <mapper resource="mappers/UserMapper.xml"/> </mappers> </configuration>
2.核心配置文件详解
(1)标签顺序
- 核心配置文件中configuration标签下的子标签要按照一定的顺序书写
- properties => settings => typeAliases => typeHandlers => objectFactory => objectWrapperFactory => reflectorFactory => plugins => environments => databaseIdProvider => mappers
(2)标签详解
- <properties>标签
- 用于引入某个properties配置文件,是一个单标签
- resource属性指定配置文件
- 范例
<properties resource="jdbc.properties" />
- <typeAliases>标签
- 用于为某个类的全类名设置别名,子标签是<typeAlias>
- 一个子标签对应设置一个类的别名
- 子标签下有type和alias两个属性,type指定需要设置别名的类的全类名,alias指定别名
- 如果只设置了type属性,那么默认的别名就是它的类名(不是全类名)而且不区分大小写
- 如果想要设置某个包下所有类的别名,可以使用<package>标签,用name属性指定包名
- 范例
<typeAliases> <typeAlias type="com.lxq.pojo.User" alias="User"></typeAlias> <package name="com.lxq.pojo"></package> </typeAliases>
- MyBatis中内建了一些类型的别名,常见的有
Java类型 |
别名 |
int |
_int或_integer |
Integer |
int或integer |
String |
string |
List |
list |
Map |
map |
- <property>标签
- 用于配置连接数据库时用到的各种属性,是一个单标签
- 该标签有两个属性,一个是name指定属性名,另一个是value指定属性值
- 如果不使用<properties>标签引入相关配置文件时,使用方式如下
<property name="driver" value="com.mysql.jdbc.Driver" />
- 如果使用<properties>标签引入相关的配置文件时,value属性可以写成如下形式
<property name="driver" value="${jdbc.driver}" />
其中配置文件的内容是
注意:这里使用jdbc.driver来给键命名是因为核心配置文件中可能会引入其他的配置文件,如果使用driver来命名键的话有可能会跟其他配置文件中的键同名而产生冲突
- <mappers>标签
- 该标签用于引入映射文件
- 每个映射文件使用子标签<mapper>来表示,该子标签是一个单标签
- 子标签<mapper>使用属性resource来指定需要引入的映射文件
- 如果想要将某个包下所有的映射文件都引入,可以使用<package>标签,使用name属性来指定需要引入的包
- 范例
<mappers> <mapper resource="mappers/UserMapper.xml" /> <package name="com.lxq.mapper" /> </mappers>
注意:使用包的形式引入映射文件需要满足两个条件,1.mapper接口所在的包和映射文件所在的包要一致;2.mapper接口名和映射文件名要相同
五.相关API
1.Resources
- Resources类由MyBatis提供用于获取来自核心配置文件的输入流
- 相关方法是:
InputStream getResourceAsStream(String filepath),注意这是一个静态方法
2.SqlSessionFactoryBuilder
- SqlSessionFactoryBuilder类由MyBatis提供用于获取SqlSessionFactory的实例对象
- 相关方法是:
SqlSessionFactory build(InputStream is),该方法通过一个输入流返回了SqlSessionFactory对象
3.SqlSessionFactory
- SqlSessionFactory类由MyBatis提供用于获取SqlSession对象,每个基于MyBatis的应用都是以一个SqlSessionFactory的实例为核心的
- 相关方法:
SqlSession openSession()和SqlSession openSession(boolean autoCommit),这两个方法都用于获取SqlSession对象,如果使用有参数的可以指定是否自动提交事务,没有指定参数的默认是不自动提交事务
4.SqlSession
- SqlSession类由MyBatis提供用于执行SQL、管理事务、接口代理
- 常用方法
方法 |
说明 |
void commit() |
提交事务 |
void rollback() |
回滚事务 |
T getMapper(Class<T> aClass) |
获取指定接口的代理实现类 |
void close() |
释放资源 |
- 除了以上常用方法外,SqlSession还有很多有关数据库增删改查的方法,但是这些方法繁琐而且不符合类型安全,所以使用getMapper()方法来获取一个Mapper接口的代理实现类来执行映射语句是个比较方便的做法
5.最佳实践
- SqlSessionFactoryBuilder
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 实例的最佳范围是方法范围(也就是局部方法变量)。你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但是最好还是不要让其一直存在以保证所有的 XML 解析资源开放给更重要的事情 - SqlSessionFactory
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由对它进行清除或重建。使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏味道(bad smell)”。因此 SqlSessionFactory 的最佳范围是应用范围。有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式 - SqlSession
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的范围是请求或方法范围。绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。也绝不能将 SqlSession 实例的引用放在任何类型的管理范围中,比如 Serlvet 架构中的 HttpSession。如果你现在正在使用一种 Web 框架,要考虑 SqlSession 放在一个和 HTTP 请求对象相似的范围中。换句话说,每次收到的 HTTP 请求,就可以打开一个 SqlSession,返回一个响应,就关闭它。这个关闭操作是很重要的,你应该把这个关闭操作放到 finally 块中以确保每次都能执行关闭 - 工具类
public class SqlSessionUtil { private static SqlSessionFactory sqlSessionFactory; static{ try { InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); sqlSessionFactory = sqlSessionFactoryBuilder.build(is); } catch (IOException e) { e.printStackTrace(); } } public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(true); } }
六.缓存
1.一级缓存
- 一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问,一级缓存是默认开启的
- 一级缓存失效的四种情况:
- 使用另一个SqlSession
- 同一个SqlSession但是查询条件不同
- 同一个SqlSession但是两次查询中间执行了任何一次增删改操作
- 同一个SqlSession但是两次查询中间手动清空了缓存,手动清空缓存的方法是调用SqlSession的
clearCache()方法
2.二级缓存
- 二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存,此后若再次执行相同的查询语句,结果就会从缓存中获取
- 二级缓存开启的条件:
- 在核心配置文件中,设置全局配置属性cacheEnabled="true",默认为true,不需要设置
- 在映射文件中设置标签<cache/>
- 二级缓存必须在SqlSession关闭或提交之后有效
- 查询的数据所转换的实体类类型必须实现序列化的接口
- 二级缓存失效的情况:两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效
- <cache/>可以设置的一些属性:
- eviction属性:缓存回收策略,默认的是 LRU
- LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象
- FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们
- SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象
- WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象
- flushInterval属性:刷新间隔,单位是毫秒,默认情况下不设置也就是没有刷新间隔,缓存仅仅调用语句时刷新
- size属性:引用数目,正整数代表缓存最多可以存储多少个对象,太大容易导致内存溢出
- readOnly属性:只读, 取值是true/false
- true:只读缓存,会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,这提供了很重要的性能优势
- false:读写缓存,会返回缓存对象的拷贝(通过序列化),这会慢一些,但是安全,因此默认是false
3.缓存的查询顺序
- 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用
- 如果二级缓存没有命中,再查询一级缓存
- 如果一级缓存也没有命中,则查询数据库
- 注意SqlSession关闭之后,一级缓存中的数据才会写入二级缓存