MyBatis
概述
MyBatis是一个和数据库进行交互,持久层框架
传统JDBC
- 编写困难
- SQL语句以硬编码的形式写在程序中,产生耦合,不利于后期维护和升级
MyBatis
- 将重要的步骤,SQL语句的编写抽取出来可以进行人为定制,其它步骤交由框架自动完成
- SQL语句写在配置文件中,方便进行后期维护和升级
- 完全解决数据库的优化问题
- 既将Java代码与SQL语句解耦合,也不会失去自动化功能,半自动化的持久层框架
- MyBatis是一个轻量级的框架,底层是对原生JDBC的简单封装
流程
-
环境搭建
- 创建一个Java工程
- 创建测试库,测试表,以及封装数据的JavaBean,和操作数据库的dao接口
-
使用MyBatis操作数据库
-
导包
mybatis-3.5.2.jar
mysql-connector-java-5.1.37-bin.jar
log4j-1.2.17.jar(日志包用于调试,依赖于类路径下log4j.xml配置文件) -
写配置
全局配置文件,指导MyBatis如何正确运行
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <!--配置连接池--> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis_1015"/> <property name="username" value="root"/> <property name="password" value="1015"/> </dataSource> </environment> </environments> <!--引入自定义的sql映射文件--> <mappers> <!--resource:默认从类路径下开始--> <mapper resource="EmployeeDao.xml"/> </mappers> </configuration>
SQL映射文件,编写dao方法执行的SQL语句,相当于接口的实现类,配置之后需在全局配置文件中注册
<?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"> <!--名称空间:接口全类名,即表示该配置文件作为哪个接口的实现类--> <mapper namespace="com.th1024.dao.EmployeeDao"> <!--Employee getEmpById(Integer id);--> <!-- select标签:定义一个查询操作 id:实现的方法名 resultType:方法返回值类型 #{属性名}:获取传递的属性值 --> <select id="getEmpById" resultType="com.th1024.bean.Employee"> select * from t_employee where id = #{id} </select> </mapper>
-
测试
@Test public void test01() throws IOException { //根据全局配置文件创建一个SqlSessionFactory对象 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //获取和数据库的一次会话 //SqlSession openSession = sqlSessionFactory.openSession(); //获取dao接口的实现 //EmployeeDao employeeDao = openSession.getMapper(EmployeeDao.class); //调用dao层的方法操作数据库 try (SqlSession openSession = sqlSessionFactory.openSession()) { EmployeeDao employeeDao = openSession.getMapper(EmployeeDao.class); Employee employee = employeeDao.getEmpById(1); System.out.println(employee); } }
细节
1)SqlSessionFactory创建SqlSession对象,SqlSessionFactory只需创建一个对象,SqlSession是和数据库交互的一次会话,每次需要和数据库进行交互,就需要创建一个SqlSession对象
2)SqlSession获取到的是接口的代理对象,由MyBatis自动创建
-
CRUD
使用MyBatis完成对数据库的增删改查操作
JavaBean
public class Employee {
private Integer id;
private String empName;
private Integer gender;
private String email;
Dao接口
public interface EmployeeDao {
public Employee getEmpById(Integer id);
public int updateEmp(Employee employee);
public int insertEmp(Employee employee);
public boolean deleteEmp(Integer id);
}
SQL映射文件
<?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">
<!--名称空间:接口全类名,即表示该配置文件作为哪个接口的实现类-->
<mapper namespace="com.th1024.dao.EmployeeDao">
<!--
public Employee getEmpById(Integer id);
传入单个参数:
基本类型:#{随便写}
JavaBean:#{属性名}
resultType:返回值类型
-->
<select id="getEmpById" resultType="com.th1024.bean.Employee">
select * from t_employee where id = #{id}
</select>
<!--public int updateEmp(Employee employee);
增删改不用写返回值类型,如果返回值类型为数值型(int,long)自动封装影响的行数,如果返回值类型为布尔型,影响0行则封装false,超过0行封装true
#{属性名}:自动从传入的对象中获取对应属性的值
-->
<update id="updateEmp">
update t_employee set empname = #{empName}, gender = #{gender}, email = #{email} where id = #{id}
</update>
<!--public int insertEmp(Employee employee);-->
<insert id="insertEmp">
insert into t_employee(`empname`,`gender`,`email`) values (#{empName},#{gender},#{email})
</insert>
<!--public boolean deleteEmp(Integer id);-->
<delete id="deleteEmp">
delete from t_employee where id = #{id}
</delete>
</mapper>
之前在创建全局配置文件和SQL映射文件时,都是直接将其放在类路径下,现在可以在工程中创建一个源码文件夹,将SQL映射文件放于对应包名目录下,MyBatis可以自动识别
全局配置文件的部分细节
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--使用properties标签引入外部配置文件-->
<properties resource="dbconfig.properties"></properties>
<!--进行一些mybatis运行时的设置-->
<settings>
<!--设置数据库列名以驼峰命名规则映射到JavaBean属性-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!--设置分步查询,按需加载-->
<!--lazyLoadingEnabled:延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--aggressiveLazyLoading:在3.4.1之后的版本默认为false-->
<!--<setting name="aggressiveLazyLoading" value="false"/>-->
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<!--配置连接池-->
<dataSource type="POOLED">
<!--${}取值-->
<property name="driver" value="${driverClass}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!--引入自定义的sql映射文件-->
<mappers>
<!--resource:默认从类路径下开始-->
<!--<mapper resource="EmployeeDao.xml"/>-->
<!--批量注册,name:dao所在的包名-->
<package name="com.th1024.dao"/>
</mappers>
</configuration>
SQL映射文件的部分细节
<?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">
<!--名称空间:接口全类名,即表示该配置文件作为哪个接口的实现类-->
<mapper namespace="com.th1024.dao.EmployeeDao">
<!--
public Employee getEmpById(Integer id);
传入单个参数:
基本类型:#{随便写}
JavaBean:#{属性名}
resultType:返回值类型
-->
<select id="getEmpById" resultType="com.th1024.bean.Employee">
select * from t_employee where id = #{id}
</select>
<!--
public Employee getEmpByIdAndEmpName(Integer id,String empName);
传入多个参数:无法使用#{属性名}进行取值
传入多个参数时,mybatis将参数自动封装到一个map中,key为参数的位置索引,例如#{0},#{1}或#{param1},#{param2}...#{paramN}
利用注解@Param(),可以指定封装参数时所用的key
-->
<select id="getEmpByIdAndEmpName" resultType="com.th1024.bean.Employee">
select * from t_employee where id = #{id} and empname = #{empName}
</select>
<!--
public Employee getEmpByMap(Map<String,Object> map);
传入参数为map:#{key}
拓展:所有的参数都以map形式封装,因此只需正确使用key就可以成功取值
public Employee getEmpByMap(@Param("id") Integer id,String empName,Employee employee);
取值:
id -> #{id}
empName -> #{1}或#{param2}
employee中的email属性 -> #{param3.email}
-->
<select id="getEmpByMap" resultType="com.th1024.bean.Employee">
select * from t_employee where id = #{id} and empname = #{empName}
</select>
<!--
#{}和${}的区别
id = #{id} and empname = #{empName} -> select * from t_employee where id = ? and empname = ?:参数预编译,参数位置用占位符?代替,安全
id = ${id} and empname = #{empName} -> select * from t_employee where id = 3 and empname = ?:直接进行拼串,存在sql注入问题
一般使用#{}取值,在不支持预编译的时候(例如需要传入表名)使用${}
-->
<!--public List<Employee> getAllEmps();
返回值类型为集合,resultType写的是集合的泛型-->
<select id="getAllEmps" resultType="com.th1024.bean.Employee">
select * from t_employee
</select>
<!--public Map<String,Object> getEmpByIdReturnMap(Integer id);
返回值类型map,封装一条记录-->
<select id="getEmpByIdReturnMap" resultType="map">
select * from t_employee where id = #{id}
</select>
<!--public Map<Integer,Employee> getAllEmpsReturnMap();
返回类型为map,封装多条记录,resultType写的是该map的value的泛型-->
<select id="getAllEmpsReturnMap" resultType="com.th1024.bean.Employee">
select * from t_employee
</select>
<!--public int updateEmp(Employee employee);
增删改不用写返回值类型,如果返回值类型为数值型(int,long)自动封装影响的行数,如果返回值类型为布尔型,影响0行则封装false,超过0行封装true
#{属性名}:自动从传入的对象中获取对应属性的值
-->
<update id="updateEmp">
update t_employee set empname = #{empName}, gender = #{gender}, email = #{email} where id = #{id}
</update>
<!--public int insertEmp(Employee employee);-->
<!--
useGeneratedKeys:获取自增主键
keyProperty:将获取到的自增主键的值赋值给哪个属性
-->
<insert id="insertEmp" useGeneratedKeys="true" keyProperty="id">
insert into t_employee(`empname`,`gender`,`email`) values (#{empName},#{gender},#{email})
</insert>
<!--public boolean deleteEmp(Integer id);-->
<delete id="deleteEmp">
delete from t_employee where id = #{id}
</delete>
</mapper>
resultMap自定义封装规则
<?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">
<mapper namespace="com.th1024.dao.CatDao">
<!--public Cat getCatById(Integer id);
resultMap:使用自定义结果集映射
-->
<select id="getCatById" resultMap="mycat">
select * from t_cat where id = #{id}
</select>
<!--
JavaBean对象属性名和数据库列名不一致
1. 在sql语句中起别名
2. 使用自定义结果集
-->
<!--resultMap:自定义结果集映射,id:唯一标识,type:指定为哪个对象进行映射-->
<resultMap id="mycat" type="com.th1024.bean.Cat">
<!--指定主键列的映射规则,column:数据库列名,property:属性名-->
<id column="id" property="id"/>
<!--指定普通列映射规则-->
<result column="c_name" property="name"/>
<result column="c_age" property="age"/>
<result column="c_gender" property="gender"/>
</resultMap>
</mapper>
联合查询
创建两个JavaBean以及两张数据库表,利用MyBatis实现联合查询
JavaBean
public class Key {
private Integer id;
private String keyName;
private Lock lock;
public class Lock {
private Integer id;
private String lockName;
//对应多把钥匙
private List<Key> keys;
Dao
public interface KeyDao {
public Key getKeyById(Integer id);
}
public interface LockDao {
public Lock getLockById(Integer id);
}
SQL映射文件
KeyDao.xml
<!--public Key getKeyById(Integer id);-->
<select id="getKeyById" resultMap="mykey">
SELECT k.`id`,k.`key_name`,k.`lock_id`,l.`id` lid,l.`lock_name` FROM t_key k
LEFT JOIN t_lock l ON k.`lock_id` = l.`id`
WHERE k.`id` = #{id}
</select>
<resultMap id="mykey" type="com.th1024.bean.Key">
<id property="id" column="id"/>
<result property="keyName" column="key_name"/>
<!--使用association标签完成联合查询出的对象封装,javaType:要封装的对象类型-->
<association property="lock" javaType="com.th1024.bean.Lock">
<id property="id" column="lid"/>
<result property="lockName" column="lock_name"/>
</association>
</resultMap>
LockDao.xml
<!--public Key getLockById(Integer id);-->
<select id="getLockById" resultMap="mylock">
SELECT l.`id`,l.`lock_name`,k.`id` kid,k.`key_name`,k.`lock_id`
FROM t_lock l LEFT JOIN t_key k ON l.`id`=k.`lock_id` WHERE l.`id`=#{id}
</select>
<resultMap id="mylock" type="com.th1024.bean.Lock">
<id property="id" column="id" />
<result property="lockName" column="lock_name"/>
<!--collection标签:定义集合类型元素的封装,property:指定集合属性,ofType:指定集合内元素的类型-->
<collection property="keys" ofType="com.th1024.bean.Key">
<!--标签体中指定元素封装规则-->
<id property="id" column="kid"/>
<result property="keyName" column="key_name"/>
</collection>
</resultMap>
分步查询,即在一次查询中分两步进行查询,可以完成对JavaBean属性的封装
全局配置文件中的配置
<!--设置分步查询,按需加载-->
<!--lazyLoadingEnabled:延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--aggressiveLazyLoading:在3.4.1之后的版本默认为false-->
<!--<setting name="aggressiveLazyLoading" value="false"/>-->
KeyDao.xml
<!--public Key getKeyByIdSimple(Integer id);-->
<select id="getKeyByIdSimple" resultMap="mykey01">
select * from t_key where id = #{id}
</select>
<!--分步查询,可能存在性能问题和事务问题,所以实际工作中一般采用连接查询-->
<resultMap id="mykey01" type="com.th1024.bean.Key">
<id property="id" column="id"/>
<result property="keyName" column="key_name"/>
<!--
通过select属性,可以实现mybatis自动调用属性指定的方法查询数据
传入参数?使用column属性,将属性指定的列的值传入
collection属性操作类似-->
<association property="lock" select="com.th1024.dao.LockDao.getLockByIdSimple" column="lock_id"></association>
</resultMap>
LockDao.xml
<!--public Lock getLockByIdSimple(Integer id);-->
<select id="getLockByIdSimple" resultType="com.th1024.bean.Lock">
select * from t_lock where id = #{id}
</select>
动态SQL
通过MyBatis的if, choose, when, otherwise, trim, where, set, foreach等标签,可组合成非常灵活的SQL语句,提高SQL语句的准确性
Dao
public interface TeacherDao {
public Teacher getTeacherById(Integer id);
public List<Teacher> getTeacherByConditions(Teacher teacher);
public List<Teacher> getTeacherIn(@Param("ids") List<Integer> ids);
public List<Teacher> getTeacherByConditionsChoose(Teacher teacher);
public Integer updateTeacher(Teacher teacher);
}
基本:where标签和if标签
if标签可以进行判断,如果结果为true则拼接上标签体内的SQL语句
where标签相当于SQL语句中的where,并可以去除if标签体内的SQL语句的多余的and或or
去除字符串操作同样可以使用trim标签,不过实际开发中建议使用where标签
<!--public List<Teacher> getTeacherByConditions(Teacher teacher);-->
<select id="getTeacherByConditions" resultType="com.th1024.bean.Teacher">
select * from t_teacher
<!--<where>-->
<!--
trim标签:截取字符串
prefix:为整体添加一个前缀
prefixOverrides:去除位于sql语句前面的多余字符串
suffix:为整体添加一个后缀
suffixOverrides:去除位于sql语句后面的多余的字符串
推荐仍然使用where标签,并将所有的and写在sql语句的前面
-->
<trim prefix="where" prefixOverrides="and" suffixOverrides="and">
<if test="id!=null">
id > #{id} and
</if>
<!--name!=null && name!="" OGNL表达式,可以编写强大的判断条件-->
<if test="name!=null && !name.equals("")">
name like #{name} and
</if>
<if test="birth!=null">
birth < #{birth} and
</if>
</trim>
<!--</where>-->
</select>
foreach标签,遍历指定集合
<!--public List<Teacher> getTeacherIn(List<Integer> ids);-->
<select id="getTeacherIn" resultType="com.th1024.bean.Teacher">
select * from t_teacher where id in
<!--
foreach:遍历指定集合
collection:指定要遍历的集合
separator:指定元素之间的分隔符
item:为当前遍历出的元素指定一个变量名
open:拼接到sql语句时以什么字符串开始
close:拼接到sql语句时以什么字符串结束
index:索引
如果遍历的集合为一个List,则index为当前索引,item为当前遍历的元素的值
如果遍历的集合是一个Map,则index为key,item为value
-->
<foreach collection="ids" close=")" item="id_item" open="(" separator=",">
#{id_item}
</foreach>
</select>
choose标签,查询条件中那个只要有任意一个查询条件满足即可,类似Java的Switch语句
<!--public List<Teacher> getTeacherByConditionsChoose(Teacher teacher);-->
<select id="getTeacherByConditionsChoose" resultType="com.th1024.bean.Teacher">
<include refid="selectSql"></include>
<where>
<choose>
<when test="id!=null">
id = #{id}
</when>
<when test="name!=null and !name.equals("")">
name = #{name}
</when>
<when test="birth!=null">
birth<#{birth}
</when>
<otherwise>
1=1
</otherwise>
</choose>
</where>
</select>
sql标签,抽取可重用的sql语句,配合include标签使用
<!--抽取可重用的sql语句,配合include标签使用-->
<sql id="selectSql">select * from t_teacher</sql>
set标签
<!--public Integer updateTeacher(Teacher teacher);-->
<update id="updateTeacher">
update t_teacher
<set>
<if test="name!=null and !name.equals("")">
name = #{name},
</if>
<if test="course!=null and !course.equals("")">
course = #{course},
</if>
<if test="address!=null and !address.equals("")">
address = #{address},
</if>
<if test="birth!=null">
birth = #{birth}
</if>
</set>
<where>
id = #{id}
</where>
</update>
缓存
MyBatis有缓存机制,可以暂时存储一些数据(存储在一个Map中),加快系统的查询速度
一级缓存,属于线程级别的缓存,SqlSession级别,默认存在
二级缓存,属于全局范围的缓存,namespace级别,需要进行配置才可以使用
一级缓存失效的几种情况
- 不同的SqlSession对应不同的一级缓存
- 同一个SqlSession但是查询条件不同
- 同一个SqlSession两次查询期间执行了任意一次增删改等操作
- 同一个SqlSession两次查询期间手动清空了缓存
SqlSession提交或关闭之后,一级缓存中的数据会转移到二级缓存中
如何使用二级缓存?
全局配置文件中配置二级缓存开启
<!--配置开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
配置某个SQL映射文件,让其使用二级缓存
<!--使用二级缓存-->
<cache></cache>
缓存原理