一、MyBatis概述
MyBatis是一款半自动的ORM持久层框架,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集,具有较高的SQL灵活性,支持高级映射(一对一,一对多),动态SQL,延迟加载和缓存等特性
ORM:Object Relation Mapping,对象关系映射,即Java对象与数据库表的映射关系
半自动:用Mybatis进行开发,需要手动编写SQL语句。而全自动的ORM框架,如hibernate,则不需要编写SQL语句,只需要定义好ORM映射关系,就可以直接进行CRUD操作
SQL灵活性:由于MyBatis需要手写SQL语句,所以它有较高的灵活性
二、搭建MyBatis
1.创建Maven工程
<dependencies>
<!-- Mybatis核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.3</version>
</dependency>
</dependencies>
2.创建核心配置文件
习惯上命名为mybatis-config.xml(将来整合Spring之后此配置文件可省略)
核心配置文件主要用于配置连接数据库的环境及MyBatis全局配置信息,放在src/main/resources下
<?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"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--引入映射文件-->
<mappers>
<mapper resource="mappers/UserMapper.xml"/>
</mappers>
</configuration>
3.创建Mapper接口
Mapper接口相当于Javaweb中的DAO,区别是不需要提供实现类,放在src/main/java/mapper下
public interface UserMapper {
// 添加用户信息
int insertUser();
}
4.创建MyBatis映射文件
命名方法:表中对应的实体类的类名 + Mapper.xml,放在src/main/resources/mappers下
MyBatis映射文件用于编写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">
<!--namespace命名空间对应Mapper接口-->
<mapper namespace="mapper.UserMapper">
<!--对应Mapper接口中的方法int insertUser();-->
<!--id与方法名一致-->
<insert id="insertUser">
insert into t_user values(null,'张三','123',23,'女')
</insert>
</mapper>
5.测试
@Test
public void testMyBatis() throws IOException {
//加载核心配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//获取SqlSessionFactoryBuilder
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//获取sqlSessionFactory
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
//获取SqlSession ture为自动提交事务
SqlSession sqlSession = sqlSessionFactory.openSession(true);
//获取mapper接口实现类
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//测试功能
int result = mapper.insertUser();
//提交事务
//sqlSession.commit();
System.out.println("result:"+result);
}
sqlSession代表Java与数据库的会话,sqlSessionFactory是生产sqlSession的工厂,可以将上述代码封装为sqlSessionUtils类
public class SqlSessionUtils {
public static SqlSession getSqlSession(){
SqlSession sqlSession = null;
try {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
sqlSession = sqlSessionFactory.openSession(true);
} catch (IOException e) {
e.printStackTrace();
}
return sqlSession;
}
}
三、增删改查
<!--void updateUser();-->
<update id="updateUser">
update t_user set username = '张三' where id = 4
</update>
<!--void deleteUser();-->
<delete id="deleteUser">
delete from t_user where id = 5
</delete>
<!--int insertUser();-->
<insert id="insertUser">
insert into t_user values(null,'admin','123456',23,'男','12345@qq.com')
</insert>
<!--
查询功能的标签必须设置resultType或resultMap
resultType:自动映射,用于属性名和表中字段名一致的情况
resultMap:自定义映射,用于一对多或多对一或字段名和属性名不一致的情况(后文详解)
-->
<!--User getUserById();-->
<select id="getUserById" resultType="pojo.User">
select * from t_user where id = 3
</select>
<!--List<User> getAllUser(); 多条数据用集合接收-->
<select id="getAllUser" resultType="pojo.User">
select * from t_user
</select>
四、获取SQL语句中的参数值
推荐通过#{ }方式获取参数值(本质为占位符),也可以使用${ }(字符串拼接,需要加单引号)
1.直接获取参数
Mapper接口:
User checkLogin(String username, String password);
映射文件:
<select id="checkLogin" resultType="User">
<!--select * from t_user where username = #{arg0} and password = #{arg1}-->
select * from t_user where username = '${param1}' and password = '${param2}'
</select>
2.使用@Param注解命名参数
Mapper接口:
User checkLoginByParam(@Param("username") String username, @Param("password") String password);
映射文件:
<select id="checkLoginByParam" resultType="User">
<!--select * from t_user where username = #{param1} and password = #{param2}-->
select * from t_user where username = #{username} and password = #{password}
</select>
3.接口参数为实体类
Mapper接口:
int insertUser(User user);
映射文件:
<!--通过实体类属性名获取参数值(本质为get方法)-->
<insert id="insertUser">
insert into t_user values(null,#{username},#{password},#{age},#{sex},#{email})
</insert>
4.接口参数为map
Mapper接口:
User checkLoginByMap(Map<String, Object> map);
映射文件:
<!--通过map中的键获取值-->
<select id="checkLoginByMap" resultType="User">
select * from t_user where username = #{username} and password = #{password}
</select>
五、字段名与属性名不一致
数据库的命名方式为_连接,而Java中使用驼峰命名法,下面介绍在MyBatis中如何解决
(最原始的方式是在SQL语句中为字段名设置别名,再此不做详解)
1.设置核心配置文件
显然MyBatis的设计者也想到了这个问题,并给出了解决方案
<configuration>
<settings>
<!--将表中字段的下划线自动转换为驼峰-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
</configuration>
2.自定义映射resultMap
我们还可以自定义字段与属性之间的映射关系,更为灵活
(1)单表映射
<!--
resultMap:设置自定义映射关系 type:设置映射关系中的实体类类型
子标签:
id:设置主键的映射关系 result:设置普通字段的映射关系
属性:
property:设置映射关系中的属性名,必须是type属性所设置的实体类类型中的属性名
column:设置映射关系中的字段名,必须是sql语句查询出的字段名
-->
<resultMap id="empResultMap" type="Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
</resultMap>
<!--List<Emp> getAllEmp();-->
<select id="getAllEmp" resultMap="empResultMap">
select * from t_emp
</select>
(2)多对一映射
数据库表的多对一关系体现在Java中为一个实体类对象作为另一个实体类对象的属性
<!--方式一:级联属性赋值-->
<resultMap id="empAndDeptResultMapOne" type="Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
<result property="dept.did" column="did"></result>
<result property="dept.deptName" column="dept_name"></result>
</resultMap>
<!--方式二:association-->
<resultMap id="empAndDeptResultMapTwo" type="Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
<!--
association:处理多对一的映射关系
property:需要处理多对的映射关系的属性名
javaType:该属性的类型
-->
<association property="dept" javaType="Dept">
<id property="did" column="did"></id>
<result property="deptName" column="dept_name"></result>
</association>
</resultMap>
<!--Emp getEmpAndDept(@Param("eid") Integer eid);-->
<select id="getEmpAndDept" resultMap="empAndDeptResultMapTwo">
select * from t_emp left join t_dept on t_emp.did = t_dept .did where t_emp.eid = #{eid}
</select>
<!--方式三:分步查询-->
<resultMap id="empAndDeptByStepResultMap" type="Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
<!--
select:设置分步查询的sql的唯一标识(namespace.SQLId或mapper接口的全类名.方法名)
column:设置分布查询的条件
fetchType:当开启了全局的延迟加载之后,可通过此属性手动控制延迟加载的效果
fetchType="lazy|eager":lazy表示延迟加载,eager表示立即加载
-->
<association property="dept"
select="com.atguigu.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo"
column="did"
fetchType="eager"></association>
</resultMap>
<!--Emp getEmpAndDeptByStepOne(@Param("eid") Integer eid);-->
<select id="getEmpAndDeptByStepOne" resultMap="empAndDeptByStepResultMap">
select * from t_emp where eid = #{eid}
</select>
(3)一对多映射
一对多映射在Java中体现在一个实体类对象的集合作为另一个实体类的属性
<resultMap id="deptAndEmpResultMap" type="Dept">
<id property="did" column="did"></id>
<result property="deptName" column="dept_name"></result>
<!--
collection:处理一对多的映射关系
ofType:表示该属性所对应的集合中存储数据的类型
-->
<collection property="emps" ofType="Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
</collection>
</resultMap>
<!--Dept getDeptAndEmp(@Param("did") Integer did);-->
<select id="getDeptAndEmp" resultMap="deptAndEmpResultMap">
select * from t_dept left join t_emp on t_dept.did = t_emp.did where t_dept.did = #{did}
</select>
<!--分布查询-->
<resultMap id="deptAndEmpByStepResultMap" type="Dept">
<id property="did" column="did"></id>
<result property="deptName" column="dept_name"></result>
<collection property="emps"
select="com.atguigu.mybatis.mapper.EmpMapper.getDeptAndEmpByStepTwo"
column="did" fetchType="eager"></collection>
</resultMap>
<!--Dept getDeptAndEmpByStepOne(@Param("did") Integer did);-->
<select id="getDeptAndEmpByStepOne" resultMap="deptAndEmpByStepResultMap">
select * from t_dept where did = #{did}
</select>
六、动态SQL
1.if
判断test中的语句是否为真
<select id="getEmpByConditionOne" resultType="Emp">
select * from t_emp where 1=1
<if test="empName != null and empName != ''">
emp_name = #{empName}
</if>
</select>
2.where
where一般与if一同使用,在if成立时会在语句后加上where关键字再添加if中的内容,如果所有的if都为false,则不会添加where关键字
<select id="getEmpByConditionTwo" 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>
</where>
</select>
3.trim
trim用于去掉或添加标签中的内容
常用属性: prefix:在trim标签中的内容的前面添加某些内容
prefixOverrides:在trim标签中的内容的前面去掉某些内容
suffix:在trim标签中的内容的后面添加某些内容
suffixOverrides:在trim标签中的内容的后面去掉某些内容
<select id="getEmpByCondition" resultType="Emp">
select * from t_emp
<trim prefix="where" suffixOverrides="and|or">
<if test="empName != null and empName != ''">
emp_name = #{empName} and
</if>
<if test="age != null and age != ''">
age = #{age} or
</if>
</trim>
</select>
4.choose { when otherwise }
相当于switch{case default},即有一个条件满足则不再执行其他条件,无条件满足时执行otherwise
<select id="getEmpByChoose" resultType="Emp">
select * from t_emp
<where>
<choose>
<when test="empName != null and empName != ''">
emp_name = #{empName}
</when>
<when test="age != null and age != ''">
age = #{age}
</when>
<otherwise>
did = 1
</otherwise>
</choose>
</where>
</select>
5.foreach
循环实现批量操作
<!--int deleteMoreByArray(@Param("eids") Integer[] eids);-->
<delete id="deleteMoreByArray">
delete from t_emp where
<!--separator为分隔符-->
<foreach collection="eids" item="eid" separator="or">
eid = #{eid}
</foreach>
<!--
delete from t_emp where eid in
open、close分别为开始和结束处添加的内容
<foreach collection="eids" item="eid" separator="," open="(" close=")">
#{eid}
</foreach>
-->
</delete>
<!--int insertMoreByList(@Param("emps") List<Emp> emps);-->
<insert id="insertMoreByList">
insert into t_emp values
<foreach collection="emps" item="emp" separator=",">
(null,#{emp.empName},#{emp.age},#{emp.sex},#{emp.email},null)
</foreach>
</insert>
6.sql片段
记录一段常用的sql片段,使用时通过标签引入
<sql id="empColumns">eid,emp_name,age,sex,email</sql>
<select id="getEmpByCondition" resultType="Emp">
select <include refid="empColumns"></include> from t_emp
</select>
七、缓存
1.一级缓存
一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据时就会从缓存中直接获取,不会从数据库重新访问
使一级缓存失效的四种情况:不同的SqlSession对应不同的一级缓存、同一个SqlSession但是查询条件不同、同一个SqlSession两次查询期间执行了任何一次增删改操作、同一个SqlSession两次查询期间手动清空了缓存
2.二级缓存
二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取(一个数据源对应一个SqlSessionFactory,二级缓存确保了不会存在脏读)
二级缓存开启条件:在核心配置文件中,设置全局配置属性cacheEnabled="true"(默认为true,不需要设置)、在映射文件中设置标签<cache />、二级缓存必须在SqlSession关闭或提交之后有效 d>、查询的数据所转换的实体类类型必须实现序列化的接口
使二级缓存失效的情况: 两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效
3.缓存查询顺序
先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用
如果二级缓存没有命中,再查询一级缓存
如果一级缓存也没有命中,则查询数据库
SqlSession关闭之后,一级缓存中的数据会写入二级缓存