Mybatis
1.入门
1.创建工程,引入坐标
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<ar tifactId>mybatis</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
2.配置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="mysql">
<!--环境配置,可以存在多个-->
<environment id="mysql">
<!--使用了JDBC的事务管理-->
<transactionManager type="JDBC">
</transactionManager>
<!--先配置为POOLED,代表以池的形式管理连接-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://127.0.0.1:3306/es" />
<property name="username" value="root"/>
<property name="password" value="adminadmin"/>
</dataSource>
</environment>
</environments>
</configuration>
3.xml映射文件
文件位置:\resources\com\itheima\mapper\UserMapper.xml
<?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="userMapper">
<insert id="save" parameterType="com.itheima.domain.User">
INSERT INTO USER(NAME,PASSWORD,email,phoneNumber,birthday) VALUE(#{name},#{password},#{email},#{phoneNumber},#{birthday});
</insert>
</mapper>
4.将Mapp文件加入到主配置文件中
<?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="mysql">
<!--省略数据库配置-->
</environments>
<!--引入映射文件-->
<mappers>
<mapper resource="com/itheima/mapper/UserMapper.xml" />
</mappers>
</configuration>
5.测试
//保存
@Test
public void testSave() throws Exception {
User user = new User(); user.setName("传智播客");
user.setPassword("admin"); user.setBirthday(new Date());
user.setEmail("admin@itcast.cn"); user.setPhoneNumber("110");
//1 加载Mybatis的主配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//2 创建sqlSessionFactory工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//3 创建sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//4.执行操作
sqlSession.insert("userMapper.save",user);
//5 提交事务
sqlSession.commit();
//6 释放资源
sqlSession.close();
}
小结:
基于接口代理方式的开发只需要程序员编写 Mapper 接口,Mybatis 框架会为我们动态生成实现类的对象。
这种开发方式要求我们遵循一定的规范:
- Mapper接口的类路径与Mapper.xml 文件中的namespace相同
- Mapper接口方法名和Mapper.xml中定义的每个statement的id相同
- Mapper接口方法的输入参数类型和Mapper.xml中定义的每个sql的parameterType的类型相同
- Mapper接口方法的输出参数类型和Mapper.xml中定义的每个sql的resultType的类型相同
2.Mybatis基于接口代理方式的内部执行原理
我们的Dao层现在只有一个接口,而接口是不实际干活的,那么是谁在做save的实际工作呢?
下面通过追踪源码看一下:
1、通过追踪源码我们会发现,我们使用的mapper实际上是一个代理对象,是由MapperProxy代理产生的。
2、追踪MapperProxy的invoke方法会发现,其最终调用了mapperMethod.execute(sqlSession, args)
3、进入execute方法会发现,最终工作的还是sqlSession。
3.Mybatis基本原理:
4.Mybatis的API
Resources
加载mybatis的配置文件。
SqlSessionFactoryBuilder
利用Resources指定的资源,将配置信息加载到内存中,还会加载mybatis配置文件中指定的所有映射配置信息, 并用特定的对象实例进行保存,从而创建SqlSessionFactory对象。
SqlSessionFactory
这是一个工厂对象,对于这种创建和销毁都非常耗费资源的重量级对象,一个项目中只需要存在一个即可。
也就是说,它的生命周期跟项目的生命周期是一致的(项目不死,我不销毁)
它的任务是创建SqlSession。
SqlSession
这是Mybatis的一个核心对象。我们基于这个对象可以实现对数据的CRUD操作。
对于这个对象应做到每个线程独有,每次用时打开,用完关闭。
抽取工具类
- 抽取基本代码工具类
public class MybatisUtil { private static SqlSessionFactory sqlSessionFactory = null; static { try { InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (Exception e) { throw new RuntimeException("加载配置文件失败"); } } public static SqlSession openSession() { return sqlSessionFactory.openSession(); } }
2 抽取测试基类
public class BaseMapperUtil {
protected SqlSession sqlSession = null;
@Before
public void init() {
sqlSession = MybatisUtil.openSession();
}
@After
public void destory() {
sqlSession.commit();
sqlSession.close();
}
}
5.基于接口代理实现CRUD
//Mapper接口
public interface UserMapper {
//保存
void save(User user);
//根据UID查询
User findByUid(Integer i);
//根据UID更新
void update(User user);
//根据ID删除
void deleteByUid(Integer uid);
}
//Mapper.xml
<mapper namespace="com.itheima.mapper.UserMapper">
<insert id="save" parameterType="com.itheima.domain.User">
insert into
user(name,password,email,phoneNumber,birthday)
value(#{name},#{password},#{email},#{phoneNumber},#{birthday})
</insert>
<select id="findByUid" parameterType="int" resultType="com.itheima.domain.User">
select * from user where uid = #{id}
</select>
<update id="update" parameterType="com.itheima.domain.User">
update user set password = #{password} where uid = #{uid}
</update>
<update id="deleteByUid" parameterType="int">
delete from user where uid = #{uid}
</update>
</mapper>
//测试类
public class CRUDTest extends BaseMapperUtil {
//保存
@Test
public void testSave() throws Exception {
User user = new User();
user.setName("传智播客3");
user.setPassword("admin");
user.setBirthday(new Date());
user.setEmail("admin@itcast.cn");
user.setPhoneNumber("110");
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.save(user);
}
//根据UID查询
@Test
public void testFindByUid() {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.findByUid(1);
System.out.println(user);
}
//根据UID修改
@Test
public void testUpdate() {
User user = new User();
user.setUid(1);
user.setPassword("adminadmin");
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.update(user);
}
//根据ID删除
@Test
public void testDeleteByUid() {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.deleteByUid(2);
}
}
5.1查询
<!--当仅仅有一个查询条件的时候
parameterType="" 可以省略
#{} 占位符可以随便写,只要不为空可以【因为参数只要一个,所以名字就不重要了,但是推荐跟名称一致】
-->
<select id="findByEmail" resultType="com.itheima.domain.User">
select * from user where email = #{emailFFFFFFFFFF}
</select>
多个条件
第一种方式:
不使用注解,这时候xml中的SQL语句中只能使用arg 或者param 获取参数
//根据多个条件查询
List<User> findByEmailAndphoneNumber(String email, String phoneNumber);
<select id="findByEmailAndphoneNumber" resultType="com.itheima.domain.User">
<!--select * from user where email = #{arg0} and phoneNumber = #{arg1}-->
select * from user where email = #{param1} and phoneNumber = #{param2}
</select>
第二种方式:
使用注解,引入@Param()注解,这时候就可以是用属性名称获取参数了
//根据多个条件查询
List<User> findByEmailAndphoneNumber(@Param("email") String email,
@Param("phoneNumber")String phoneNumber);
<select id="findByEmailAndphoneNumber" resultType="com.itheima.domain.User">
select * from user where email = #{email} and phoneNumber = #{phoneNumber}
</select>
第三种方式(推荐使用):
使用pojo对象传递参数
//使用对象封装查询条件
List<User> findByEmailAndphoneNumber2(User user);
<select id="findByEmailAndphoneNumber2" resultType="com.itheima.domain.User">
select * from user where email = #{email} and phoneNumber = #{phoneNumber}
</select>
5.2模糊查询
<!--方式一: 传入参数的时候,直接传入setEmail(%传智%)-->
<select id="findByName1" resultType="com.itheima.domain.User">
select * from user where name like #{email}
</select>
<!--方式二: 在SQL语句中直接控制模糊模式'%' 传入参数的时候,直接传入setEmail(传智)-->
<select id="findByName2" resultType="com.itheima.domain.User">
select * from user where name like "%"#{email}"%"
</select>
<!--方式三: 使用${}赋值,注意$不表示占位符,而是表示字符串拼接,而且要求接口方法中要使用@Param声明-->
<select id="findByName3" resultType="com.itheima.domain.User">
select * from user where name like "%${email}%"
</select>
<!--方式四: 使用MySQL的函数, 推荐!!!-->
<select id="findByName4" resultType="com.itheima.domain.User">
select * from user where name like concat('%',#{email},'%');
</select>
面试直达:请说出Mybatis中#{}和${}的区别
#{}表示占位符,${}表示字符串拼接
${}可能会引起SQL注入问题,#{}不会
两者都可以接受简单类型的值和pojo类型的属性值,但是${}接收简单类型数据只能使用${value},#{} 可以是随意值
6.返回主键
6.1 useGeneratedKeys
使用useGeneratedKeys="true" 声明返回主键
使用keyProperty指定将主键映射到pojo的哪个属性
<insert id="saveReturnId" useGeneratedKeys="true" keyProperty="uid">
insert into
user(name,password,email,phoneNumber,birthday)
value(#{name},#{password},#{email},#{phoneNumber},#{birthday})
</insert>
6.2 selectKey
<insert id="saveReturnId">
<!--
keyColumn 指定主键列名称
keyProperty 指定主键封装到实体的哪个属性
resultType 指定主键类型
order 指定在数据插入数据库前(后),执行此语句
-->
<selectKey keyProperty="uid" keyColumn="uid" resultType="int" order="AFTER">
select last_insert_id()
</selectKey>
insert into
user(name,password,email,phoneNumber,birthday)
value(#{name},#{password},#{email},#{phoneNumber},#{birthday})
</insert>
7.动态SQL
根据程序运行时传入参数的不同而产生SQL语句结构不同,就是动态SQL。
findByCondtion(User user):根据传入的user对象进行查询,将不为空的属性作为查询条件
用户输入的是: 用户名和密码
select * from user where name= #{name} and password = #{password}
用户输入的是: 用户名和邮箱
select * from user where name= #{name} and email = #{email}
用户输入的是: 用户名和和密码和邮箱
select * from user where name= #{name} and password = #{password} and email = #{email}
动态SQL是Mybatis的强大特性之一,Mybatis3之后,需要了解的动态SQL标签仅仅只有下面几个:
- if choose (when, otherwise) 用于条件判断
- trim (where, set) 用于去除分隔符
- foreach 用于循环遍历
7.1 条件判断
7.1.1 if
if 用来做条件判断,接受一个ognl表达式,返回一个boolean值。< if test="ognl表达式">sql片段< /if>。
第一种情况: if 在where语句中的使用,实现动态条件判断
需求:根据传入的user对象的进行查询,将不为空的属性作为查询条件
//实现根据传入的值是否为空实现where语句拼接
//注意语句中使用了 1=1 来避免语句出现错误
<select id="findByCondition" resultType="com.itheima.domain.User">
select * from user where 1=1
<if test="name != null and name != ''">
and name like concat('%',#{name},'%')
</if>
<if test="email != null and email != ''">
and email = #{email}
</if>
</select>
第二种情况: if 在set语句中的使用,实现动态条件更新
需求:根据传入的user对象的进行更新,将不为空的属性更新到数据库
//实现根据传入的值是否为空实现set语句拼接
//注意语句中使用了 uid = #{uid} 来避免语句出现错误
<update id="update">
update user set
<if test="name != null and name != ''">
name = #{name},
</if>
<if test="email != null and email != ''">
email = #{email},
</if>
uid = #{uid} where uid = #{uid}
</update>
第三种情况: if 在insert语句中的使用,实现动态条件插入
需求:根据传入的user对象的进行插入,将不为空的属性插入到数据库
//实现根据传入的值是否为空实现insert语句拼接
<insert id="insert">
insert into
user(name,email,phoneNumber,birthday
<if test="password != null and password != ''">
,password
</if>
)
value(#{name},#{email},#{phoneNumber},#{birthday}
<if test="password != null and password != ''">
,#{password}
</if>
)
</insert>
7.1.2 choose、when、otherwise
有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用 choose 标签可以解决此类问题,类似于 Java 的if elseif 语句。
<select id="select" resultType="com.itheima.domain.User">
select * from user where 1=1
<choose>
<when test="name != null and name != ''">
and name = #{name}
</when>
<when test="email != null and email != ''">
and email = #{email}
</when>
<when test="phoneNumber != null and phoneNumber != ''">
and phoneNumber = #{phoneNumber}
</when>
<otherwise>
and 1 = 2
</otherwise>
</choose>
</select>
下面是java和mybatis中的判断语句语法的对比
7.2 格式优化
这三个关键字是用来对Mybatis中的SQL语句书写进行优化的。主要是用来去除多余的符号和关键字。
7.2.1 where
where标签的作用:
-
如果where包含的标签中有返回true的语句,Mybatis会在语句插入一个‘where’。
-
如果标签返回的内容是以AND 或OR 开头的,Mybatis会将其剔除掉。
select * from user and name like concat('%',#{name},'%') and email = #{email}
7.2.2 set
set标签的作用:去掉set语句最后的逗号(,)
注意:set标签中必须保证至少有一个语句
<update id="update2">
update user
<set>
<if test="name != null and name != ''">
name = #{name},
</if>
<if test="email != null and email != ''">
email = #{email},
</if>
uid = #{uid}
</set>
where uid = #{uid}
</update>
7.2.3 trim(了解)
trim标签是一个格式化标签,可以完成set或者是where的功能。
//使用trim替代where
//prefix 代表必要的时候在trim标签的最前面加上一个where
//prefixOverrides 代表必要的时候干掉trim生成语句中的第一个and或者or
<select id="find3" resultType="com.itheima.domain.User">
select * from user
<trim prefix="where" prefixOverrides="and | or">
<if test="name != null and name != ''">
and name like concat('%',#{name},'%')
</if>
<if test="email != null and email != ''">
and email = #{email}
</if>
</trim>
</select>
//使用trim替代set
//prefix 代表必要的时候在trim标签的最前面加上一个set
//suffixOverrides 代表必要的时候干掉trim生成语句中的最后一个,
<update id="update3">
update user
<trim prefix="set" suffixOverrides=",">
<if test="name != null and name != ''">
name = #{name},
</if>
<if test="email != null and email != ''">
email = #{email},
</if>
</trim>
where uid = #{uid}
</update>
8 循环遍历
foreach主要是用来做数据的循环遍历。
典型的应用场景是SQL中的in语法中,select * from user where uid in (1,2,3) 在这样的语句中,传入的参数部分必须依靠 foreach遍历才能实现。我们传入的参数,一般有下面几个形式:
-
集合(List Set)
-
数组
-
pojo
foreach的选项
collection:数据源【重点关注这一项,它的值会根据出入的参数类型不同而不同】
open:开始遍历之前的拼接字符串
close:结束遍历之后的拼接字符串
separator:每次遍历之间的分隔符
item:每次遍历出的数据
index:遍历的次数,从0开始
8.1 集合
<select id="find4" resultType="com.itheima.domain.User">
<!--
collection:数据源【当参数为一个集合时,此值为collection,如果参数是List,此值也可以使用list代替】
此处的值也可以通过@Param("自定义")的方式在Dao层方法上自己指定。
-->
<!--select * from user where uid in (1,2,3)-->
select * from user where uid in
<foreach collection="collection" open="(" close=")" separator="," item="item">
#{item}
</foreach>
</select>
List<User> find4(List<Integer> uids);//collection="collection"
//或者
List<User> find4(@Param("uids") List<Integer> uids);//collection="uids"
8.2数组
<select id="find5" resultType="com.itheima.domain.User">
<!--
collection:数据源【当参数为一个数组时,此值为array】
此处的值也可以通过@Param("自定义")的方式在Dao层方法上自己指定
-->
<!--select * from user where uid in (1,2,3)-->
select * from user where uid in
<foreach collection="array" open="(" close=")" separator="," item="item">
#{item}
</foreach>
</select>
List<User> find5(Integer[] uids);//collection="array"
//或者
List<User> find5(@Param("uids") Integer[] uids);//collection="uids"
8.3 pojo
<select id="find6" resultType="com.itheima.domain.User">
select * from user where uid in
<foreach collection="uids" open="(" close=")" separator="," item="item" index="index">
#{item}
</foreach>
</select>
List<User> find6(User user);//在user中封装了一个List<Integer> uids属性
总结:foreach的使用主要是用来遍历数据源。关键点在于数据源的指定:
- 如果是集合,使用collection
- 如果是数组,使用array
- 如果是pojo,使用pojo中的属性名称
小结
动态SQL的最终目的其实就是通过Mybatis提供的标签实现sql语句的动态拼装。
为了保证拼接准确,我们最好首先要写原生的sql语句出来,然后再通过mybatis动态sql对照着改。
9.多表关系
9.1 多表关系分析
数据库设计的三种表间关系分别为: 一对一 、一对多(多对一)、 多对多关系。
常见示例:
一对一:人和身份证、QQ号码和QQ详情
一对多:用户和登录日志、部门和员工
多对多:用户和角色、老师和学生
明确:我们今天只涉及实际开发中常用的关联关系,一对多和多对多。而一对一的情况,在实际开发中几乎不用。
9.2 多对一
多对一的映射方式一般有下面几种:
- 采用外键+别名的形式进行映射
- 采用resultMap形式进行映射
- 采用resultMap + association 形式进行映射 【推荐】
下面分别来看。
9.2.1 采用外键+别名的形式进行映射
<select id="findAll" resultType="com.itheima.domain.LoginInfo">
<!--select * from user u,login_info l where u.uid = l.uid;-->
select
l.*,
u.uid "user.uid",
u.name "user.name",
u.password "user.password",
u.email "user.email",
u.phoneNumber "user.phoneNumber",
u.birthday "user.birthday"
from user u,login_info l where u.uid = l.uid
</select>
9.2.2 采用resultMap形式进行映射
//注意此方式还支持继承
<resultMap id="loginInfoMap" type="com.itheima.domain.LoginInfo">
<id property="lid" column="lid" />
<result property="ip" column="ip" />
<result property="loginTime" column="loginTime" />
<result property="user.uid" column="uid" />
<result property="user.name" column="name" />
<result property="user.password" column="password" />
<result property="user.email" column="email" />
<result property="user.phoneNumber" column="phoneNumber" />
<result property="user.birthday" column="birthday" />
</resultMap>
<select id="findAll2" resultMap="loginInfoMap">
select * from user u,login_info l where u.uid = l.uid;
</select>
9.2.3 采用resultMap + association 形式进行映射
<resultMap id="loginInfoMap3" type="com.itheima.domain.LoginInfo">
<id property="lid" column="lid" />
<result property="ip" column="ip" />
<result property="loginTime" column="loginTime" />
//property代表属性名称 javaType对应真实的类型
<association property="user" javaType="com.itheima.domain.User">
<result property="uid" column="uid" />
<result property="name" column="name" />
<result property="password" column="password" />
<result property="email" column="email" />
<result property="phoneNumber" column="phoneNumber" />
<result property="birthday" column="birthday" />
</association>
</resultMap>
<select id="findAll3" resultMap="loginInfoMap3">
select * from user u,login_info l where u.uid = l.uid;
</select>
9.3 一对多
一对多的映射方式一般采用resultMap + collection形式进行实现。
<resultMap id="userMap2" extends="userMap" type="com.itheima.domain.User">
<id property="uid" column="uid"/>
<result property="name" column="name"/>
<result property="password" column="pass"/>
<result property="email" column="email"/>
<result property="phoneNumber" column="phoneNumber"/>
<result property="birthday" column="birthday"/>
//loginInfos配置属性 ofType配置指定的类
<collection property="loginInfos" ofType="com.itheima.domain.LoginInfo">
<id property="lid" column="lid" />
<result property="ip" column="ip" />
<result property="loginTime" column="loginTime" />
</collection>
</resultMap>
<select id="findAll" resultMap="userMap2">
select * from user u left join login_info l on u.uid = l.uid;
</select>
9.4 多对多
多对多的配置跟一对多很相似,难度在于SQL语句的编写。
<resultMap id="userMap3" type="com.itheima.domain.User">
<id property="uid" column="uid"/>
<result property="name" column="name"/>
<result property="password" column="pass"/>
<result property="email" column="email"/>
<result property="phoneNumber" column="phoneNumber"/>
<result property="birthday" column="birthday"/>
<collection property="roles" ofType="com.itheima.domain.Role">
<id property="rid" column="rid" />
<result property="name" column="name" />
<result property="description" column="description" />
</collection>
</resultMap>
<select id="findAll3" resultMap="userMap3">
SELECT * FROM
USER u LEFT JOIN user_role ur ON u.uid = ur.uid
JOIN role r ON ur.`rid` = r.`rid`
</select>
10.嵌套查询
10.1 什么是嵌套查询?
嵌套查询就是将原来多表查询中的联合查询语句拆成单个表的查询,再使用mybatis的语法嵌套在一起。
举个例子:
需求: 查询日志的同时查询到用户信息
联合查询: select * from login_info l,user u where l.uid = u.uid
改成嵌套查询:
1) 先查询到日志信息(这里面包含了一个跟用户相关的外键uid)
select * from login_info 得到uid
2)根据上一拿到的uid查询相关的用户信息
select * from user where uid = #{第一步得到的uid}
3)使用Mybatis提供的关键字,将上面两步嵌套起来
。。。。
10.2 多对一的嵌套查询
目标: 查询日志的同时查询到用户信息
未使用嵌套之前:
<resultMap id="baseMap" type="com.itheima.domain.LoginInfo">
<id property="lid" column="lid" />
<result property="ip" column="ip" />
<result property="loginTime" column="loginTime" />
</resultMap>
<resultMap id="loginInfoMap1" type="com.itheima.domain.LoginInfo" extends="baseMap">
<association property="user" javaType="com.itheima.domain.User">
<id property="uid" column="uid" />
<result property="name" column="name" />
<result property="password" column="password" />
<result property="email" column="email" />
<result property="phoneNumber" column="phoneNumber" />
<result property="birthday" column="birthday" />
</association>
</resultMap>
<select id="findAll1" resultMap="loginInfoMap1">
select * from login_info l,user u where l.uid = u.uid
</select>
改成嵌套查询:
- 对日志表进行单表查询
<!-- LoginInfoMapper.xml -->
<resultMap id="loginInfoMap2" type="com.itheima.domain.LoginInfo" extends="baseMap">
<association>
<!--此处暂时留空-->
</association>
</resultMap>
<select id="findAll2" resultMap="loginInfoMap2">
select * from login_info
</select>
- 在用户表的Mapper文件中:使用uid查询用户
<!--UserMapper.xml-->
<select id="findByUid" resultType="com.itheima.domain.User">
select * from user where uid = #{uid}
</select>
- 使用Mybatis提供的关键字,将上面两步嵌套起来
<!--在LoginInfoMapper.xml中进行嵌套关联-->
<resultMap id="loginInfoMap2" type="com.itheima.domain.LoginInfo" extends="baseMap">
<!--
select 指定联合查询的方法(根据uid查询User)
column 要传递的参数属性字段(将此字段的值作为参数传给select方法)
-->
<association property="user" column="uid"
select="com.itheima.mapper.UserMapper.findByUid">
</association>
</resultMap>
10.3 一对多的嵌套查询
目标: 查询用户的同时查询到关联的日志信息未使用嵌套之前:
<resultMap id="baseMap" type="com.itheima.domain.User">
<id property="uid" column="uid"/>
<result property="name" column="name"/>
<result property="password" column="password"/>
<result property="email" column="email"/>
<result property="phoneNumber" column="phoneNumber"/>
<result property="birthday" column="birthday"/>
</resultMap>
<resultMap id="userMap1" type="com.itheima.domain.User" extends="baseMap">
<collection property="loginInfos" ofType="com.itheima.domain.LoginInfo">
<id property="lid" column="lid"/>
<result property="ip" column="ip"/>
<result property="loginTime" column="loginTime"/>
</collection>
</resultMap>
<select id="findAll1" resultMap="userMap1">
select * from user u left join login_info l on u.uid = l.uid;
</select>
改成嵌套查询:
- 对用户表进行单表查询
<!--UserMapper.xml-->
<resultMap id="userMap2" type="com.itheima.domain.User" extends="baseMap">
<collection>
<!--此处暂时留空-->
</collection>
</resultMap>
<select id="findAll2" resultMap="userMap2">
select * from user
</select>
- 在日志表的Mapper文件中:使用uid查询日志
<!--LoginInfoMapper.xml-->
<select id="findByUid" resultType="com.itheima.domain.LoginInfo">
select * from login_info where uid = #{uid}
</select>
- 使用Mybatis提供的关键字,将上面两步嵌套起来
<!--UserMapper.xml-->
<resultMap id="userMap2" type="com.itheima.domain.User" extends="baseMap">
<collection property="loginInfos" column="uid"
select="com.itheima.mapper.LoginInfoMapper.findByUid">
</collection>
</resultMap>
11.加载策略
11.1 什么是加载策略
当多个模型之间存在联系时,在加载一个模型的数据的时候,是否随之加载与其相关模型数据的策略,我们称之为加载策略。
在Mybatis中,常用的加载策略有立即加载和延迟加载(懒加载)两种。
举个例子:现在有用户和用户登录日志两个模型,当加载1个用户的时候,是否需要立即加载与其相关的登录日志的信息呢?
如果需要,我们把这种加载策略成为立即加载,
如果不需要,等到真正要使用日志信息的时候再加载,我们把这种加载策略成为立即加载(懒加载)。
11.2 Mybatis加载策略
Mybatis的默认加载策略是立即加载,也就是在加载一个对象的时候会立即联合加载到其关联的对象。
当然,Mybatis也提供了修改加载策略的方法。
- 在Mybatis 的配置文件中可以使用setting修改全局的加载策略。
- 在< association >和< collection >元素中都有一个fetchType属性,该值会覆盖掉全局参数的配置。
-
fetchType=“lazy” 懒加载策略
-
fetchType=“eager” 立即加载策略
<association fetchType="eager|lazy"></association> <collection fetchType="eager|lazy"></collection>
-
注意:
- 在配置了延迟加载策略后,即使没有调用关联对象的任何方法,当你调用当前对象的equals、clone、hashCode、toString方法时也会触发关联对象的查询。
- 在配置文件中可以使用lazyLoadTriggerMethods配置项覆盖掉mybatis的默认行为。
<setting name="lazyLoadTriggerMethods" value="getUid,toString"/>
12 缓存机制
缓存是用来提高查询效率的,所有的持久层框架基本上都有缓存机制。
Mybatis有两级缓存,一级缓存是SqlSession级别的,二级缓存是SqlSessionFactory级别的。
12.1 一级缓存
一级缓存是SqlSession级别的缓存,是默认开启且无法关闭的。
public void testFindByUid(){
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user1 = mapper.findByUid(1);//发送SQL
System.out.println("==============");
User user2 = mapper.findByUid(1);//未发送SQL
System.out.println(user1 == user2);//true
}
- 同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。
- 当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。
- 不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
注意:
- 调用SqlSession的clearCache(),或者执行C(增加)U(更新)D(删除)操作,都会清空缓存。
- 查询语句中这样的配置< select flushCache=“true”/>也会清除缓存。
12.2 二级缓存
二级缓存是sqlSessionFactory级别的缓存,是默认开启,但是可以关闭的。
二级缓存是多个SqlSession共享的,不同的sqlSession两次执行相同namespace下的sql语句,第一次执行完毕会将数据库中查询的数据写到二级缓存(内存),第二次会从二级缓存中获取数据,而不再从数据库查询,从而提高查询效率。
二级缓存的设置:
<!--主配置文件中配置cacheEnable为true,这也是默认配置,可以不加-->
<setting name="cacheEnabled" value="true|false"/>
<!--在Mapper.xml文件中加入cache标签 -->
<cache />
验证:
public void testFindByUid2(){
SqlSession sqlSession1 = MybatisUtil.openSession();
User user1 = sqlSession1.getMapper(UserMapper.class).findByUid(1);//发送SQL
sqlSession1.close();
System.out.println("===================");
SqlSession sqlSession2 = MybatisUtil.openSession();
User user2 = sqlSession2.getMapper(UserMapper.class).findByUid(1);//不发送SQL
sqlSession2.close();
//注意这里得到的对象是从缓存中拷贝出来的,如果直接使用一个会有线程安全问题
System.out.println(user1 == user2);//false
}
13.配置文件
Mybatis的配置文件中有很多可配置项,我们只研究几个常用的。
注意:配置文件中的各个配置项要严格按照dtd中规定的顺序书写,可以省略某些项,但是不能颠倒顺序。
13.1 properties:引入外部配置文件
用来引入外部的配置。
我们经常把一些敏感信息单独放在一个文件中,然后通过properties 引入到配置文件。
举个例子:我们跟数据库相关的信息单独配置
1)创建一个db.properties存放相关信息。
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/es
jdbc.username=root
jdbc.password=adminadmin
2)在mybatis的配置文件中引入配置文件
<!--还支持使用<properties url="">的方式引入网络上的配置文件-->
<properties resource="db.properties" />
3 ) 在需要的地方使用el表达式引入需要的值
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
13.2 typeAlias:配置别名
mybatis支持别名机制。所谓别名,就是给比较长的名字起一个比较短的名字,在使用的时候二者等价而已。
默认支持的别名
自定义别名
mybatis的配置文件中有一个typeAliases标签,在这个标签下可以自定义别名。
- 单个定义别名
- 使用包的形式批量定义别名
当定义完别名后,下面两种使用方式就是等价的。
<select id="findByUid" resultType="com.itheima.domain.LoginInfo"></select>
<select id="findByUid" resultType="loginInfo"></select>
13.3 mappers:注册映射配置
对于将我们的Mapper文件注册到主配置文件,mybatis支持三种方式:
<!--1、直接注册Mapper映射文件-->
<mapper resource="com/itheima/dao/UserMapper.xml"/>
<!--2、直接注册Mapper接口类-->
<mapper class="com.itheima.dao.UserMapper"/>
<!--3、注册mapper包,包下的配置文件会全部被注册-->
<package name="com.itheima.mapper"/>
13.4 setting:一些常用配置
<settings>
<!--开启懒加载,默认是false-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--覆盖默认的导致懒加载失效的方法-->
<setting name="lazyLoadTriggerMethods" value=""/>
</settings>
13.5 transactionManager:事务控制
Mybatis底层使用的是JDBC的事务管理,只不过在上面封装了一下。
<transactionManager type="JDBC"></transactionManager>
-
在JDBC中我们可以通过调用Connection的setAutoCommit()方法来开关对事务的支持。
true代表自动提交事务(默认),false代表需要手动调用commit()\rollback()等事务控制方法。 -
mybatis框架是对JDBC的封装,底层也是调用Connection的setAutoCommit()方法来控制事务的。
只不过,Mybatis在JDBC的基础上做了修改,默认手动提交事务。//Mybatis的事务控制很简单主要是下面三个API
SqlSession sqlSession = SqlSessionFactory.openSession(true);//开启事务(此事务自动提交)
SqlSession sqlSession = SqlSessionFactory.openSession();//提交事务(此事务需要手动提交)SqlSession.commit();//提交事务
SqlSession.rollback();//回滚事务
13.6 dataSource: 数据源
<dataSource type="POOLED">
<!--这里的name都是数据源声明好的,必须按照这个写,每一种数据源都应该提供这些基本的参数接受项-->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
mybatis中使用了标准的 JDBC 数据源(javax.sql.DataSource)接口来配置 JDBC 连接对象,可以通过mybatis的配置文件dataSource的type属性来指定。可配置项有三个:
- UNPOOLED:不使用连接池的数据源。这个数据源的实现只是每次被请求时打开和关闭连接。
- POOLED:使用连接池的数据源。这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。它继承并扩展了UNPOOLED,增加了连接池的功能。
- JNDI:不做了解,知道就行。
思考:如何来更换连接池呢?比如我们想druid连接池,怎么办?
步骤:
-
引入druid坐标
com.alibaba druid 1.1.15
2)自定义一个工厂类继承UnpooledDataSourceFactory,在构造方法中返回DruidDataSource数据源
public class DruidDataSourceFactory extends UnpooledDataSourceFactory {
public DruidDataSourceFactory() {
this.dataSource = new DruidDataSource();
}
}
3)修改配置文件
<!--这里的type其实就是自定义产生数据源的工厂-->
<dataSource type="com.itheima.datasource.DruidDataSourceFactory">
<!--这里的name的值要根据DruidDataSource规定的方式写-->
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
使用注解书写SQL:
Mybatis除了支持在xml中书写SQL,也支持使用注解的形式编写SQL。
但这样的形式只适合书写非常简单sql语句。
public interface UserMapper {
@Insert("insert into user(name) values(#{name})")
public void insertT(User user);
@Delete("delete from user where id=#{id}")
public void deleteById(int id);
@Update("update user set name=#{name} where id=#{id}")
public void updateT(User user);
@Select("select * from user where id=#{id}")
public User getUserById(int id);
@Select("select * from user")
public List<User> getAll();
}
- 使用注解不能完成动态SQL
- 如果SQL非常复杂,那么使用注解写在java文件中,反而感觉不伦不类
- 硬编码在java文件中,如果需要修改SQL语句,必须要重新编译。