Mybatis进阶
MyBatis 接口代理方式实现 Dao 层
接口代理方式-实现规则
- 传统方式实现 Dao 层,我们既要写接口,还要写实现类。而MyBatis 框架可以帮助我们省略编写Dao 层接口实现类的步骤。程序员只需要编写接口,由MyBatis 框架根据接口的定义来创建该接口的动态代理对象。
实现规则
- 映射配置文件中的名称空间必须和Dao 层接口的全类名相同。
- 映射配置文件中的增删改查标签的id 属性必须和 Dao 层接口的方法名相同。
- 映射配置文件中的增删改查标签的parameterType 属性必须和 Dao 层接口方法的参数相同。
- 映射配置文件中的增删改查标签的resultType 属性必须和 Dao 层接口方法的返回值相同。
接口代理方式-代码实现
- 删除 mapper(dao) 层接口的实现类。
- 修改映射配置文件。
- 修改 service 层接口的实现类,采用接口代理方式实现功能。
映射配置文件StudentMapper.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!--MyBatis的DTD约束-->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
mapper:核心根标签
namespace属性:名称空间
-->
<mapper namespace="com.itheima.mapper.StudentMapper">
<sql id="select" >SELECT * FROM student</sql>
<!--
select:查询功能的标签
id属性:唯一标识
resultType属性:指定结果映射对象类型
parameterType属性:指定参数映射对象类型
-->
<select id="selectAll" resultType="student">
<include refid="select"/>
</select>
<select id="selectById" resultType="student" parameterType="int">
<include refid="select"/> WHERE id = #{id}
</select>
<insert id="insert" parameterType="student">
INSERT INTO student VALUES (#{id},#{name},#{age})
</insert>
<update id="update" parameterType="student">
UPDATE student SET name = #{name},age = #{age} WHERE id = #{id}
</update>
<delete id="delete" parameterType="int">
DELETE FROM student WHERE id = #{id}
</delete>
</mapper>
/*
持久层接口
*/
public interface StudentMapper {
//查询全部
public abstract List<Student> selectAll();
//根据id查询
public abstract Student selectById(Integer id);
//新增数据
public abstract Integer insert(Student stu);
//修改数据
public abstract Integer update(Student stu);
//删除数据
public abstract Integer delete(Integer id);
/*
业务层实现类
*/
public class StudentServiceImpl implements StudentService {
@Override
public List<Student> selectAll() {
List<Student> list = null;
SqlSession sqlSession = null;
InputStream is = null;
try{
//1.加载核心配置文件
is = Resources.getResourceAsStream("MyBatisConfig.xml");
//2.获取SqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//3.通过工厂对象获取SqlSession对象
sqlSession = sqlSessionFactory.openSession(true);
//4.获取StudentMapper接口的实现类对象
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); // StudentMapper mapper = new StudentMapperImpl();
//5.通过实现类对象调用方法,接收结果
list = mapper.selectAll();
} catch (Exception e) {
} finally {
//6.释放资源
if(sqlSession != null) {
sqlSession.close();
}
if(is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//7.返回结果
return list;
}
@Override
public Student selectById(Integer id) {
Student stu = null;
SqlSession sqlSession = null;
InputStream is = null;
try{
//1.加载核心配置文件
is = Resources.getResourceAsStream("MyBatisConfig.xml");
//2.获取SqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//3.通过工厂对象获取SqlSession对象
sqlSession = sqlSessionFactory.openSession(true);
//4.获取StudentMapper接口的实现类对象
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); // StudentMapper mapper = new StudentMapperImpl();
//5.通过实现类对象调用方法,接收结果
stu = mapper.selectById(id);
} catch (Exception e) {
} finally {
//6.释放资源
if(sqlSession != null) {
sqlSession.close();
}
if(is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//7.返回结果
return stu;
}
@Override
public Integer insert(Student stu) {
Integer result = null;
SqlSession sqlSession = null;
InputStream is = null;
try{
//1.加载核心配置文件
is = Resources.getResourceAsStream("MyBatisConfig.xml");
//2.获取SqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//3.通过工厂对象获取SqlSession对象
sqlSession = sqlSessionFactory.openSession(true);
//4.获取StudentMapper接口的实现类对象
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); // StudentMapper mapper = new StudentMapperImpl();
//5.通过实现类对象调用方法,接收结果
result = mapper.insert(stu);
} catch (Exception e) {
} finally {
//6.释放资源
if(sqlSession != null) {
sqlSession.close();
}
if(is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//7.返回结果
return result;
}
@Override
public Integer update(Student stu) {
Integer result = null;
SqlSession sqlSession = null;
InputStream is = null;
try{
//1.加载核心配置文件
is = Resources.getResourceAsStream("MyBatisConfig.xml");
//2.获取SqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//3.通过工厂对象获取SqlSession对象
sqlSession = sqlSessionFactory.openSession(true);
//4.获取StudentMapper接口的实现类对象
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); // StudentMapper mapper = new StudentMapperImpl();
//5.通过实现类对象调用方法,接收结果
result = mapper.update(stu);
} catch (Exception e) {
} finally {
//6.释放资源
if(sqlSession != null) {
sqlSession.close();
}
if(is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//7.返回结果
return result;
}
@Override
public Integer delete(Integer id) {
Integer result = null;
SqlSession sqlSession = null;
InputStream is = null;
try{
//1.加载核心配置文件
is = Resources.getResourceAsStream("MyBatisConfig.xml");
//2.获取SqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//3.通过工厂对象获取SqlSession对象
sqlSession = sqlSessionFactory.openSession(true);
//4.获取StudentMapper接口的实现类对象
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); // StudentMapper mapper = new StudentMapperImpl();
//5.通过实现类对象调用方法,接收结果
result = mapper.delete(id);
} catch (Exception e) {
} finally {
//6.释放资源
if(sqlSession != null) {
sqlSession.close();
}
if(is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//7.返回结果
return result;
}
}
接口代理方式-源码分析
- 分析动态代理对象如何生成的?
通过动态代理开发模式,我们只编写一个接口,不写实现类,我们通过getMapper() 方法最终获取到 org.apache.ibatis.binding.MapperProxy代理对象,然后执行功能,而这个代理对象正是 MyBatis使用了 JDK 的动态代理技术,帮助我们生成了代理实现类对象。从而可以进行相关持久化操作。 - 分析方法是如何执行的?
动态代理实现类对象在执行方法的时候最终调用了mapperMethod.execute() 方法,这个方法中通过 switch 语句根据操作类型来判断是新增、修改、删除、查询操作,最后一步回到了MyBatis 最原生的 SqlSession方式来执行增删改查。
接口代理方式小结
- 接口代理方式可以让我们只编写接口即可,而实现类对象由MyBatis 生成。
实现规则
- 映射配置文件中的名称空间必须和Dao 层接口的全类名相同。
- 映射配置文件中的增删改查标签的id 属性必须和 Dao 层接口的方法名相同。
- 映射配置文件中的增删改查标签的parameterType 属性必须和 Dao 层接口方法的参数相同。
- 映射配置文件中的增删改查标签的resultType 属性必须和 Dao 层接口方法的返回值相同。
- 获取动态代理对象
SqlSession 功能类中的getMapper() 方法。
MyBatis 映射配置文件 – 动态 SQL
动态 SQL 介绍
-
MyBatis 映射配置文件中,前面我们的SQL 都是比较简单的,有些时候业务逻辑复杂时,我们的SQL 就是 动态变化的,此时在前面学习的SQL 就不能满足要求了。
-
多条件查询
-
动态 SQL 标签
-
<if>:条件判断标签。
-
<foreach>:循环遍历标签。
if标签
foreach标签
- 属性
collection:参数容器类型,(list-集合,array-数组)。
open:开始的 SQL 语句。
close:结束的SQL 语句。
item:参数变量名。
separator:分隔符。
SQL 片段抽取
- 我们可以将一些重复性的SQL 语句进行抽取,以达到复用的效果。
-
<sql>:抽取SQL 语句标签。
-
<include>:引入SQL 片段标签。
动态SQL代码:
<mapper namespace="com.itheima.mapper.StudentMapper">
<sql id="select" >SELECT * FROM student</sql>
<select id="selectCondition" resultType="student" parameterType="student">
<include refid="select"/>
<where>
<if test="id != null">
id = #{id}
</if>
<if test="name != null">
AND name = #{name}
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
</select>
<select id="selectByIds" resultType="student" parameterType="list">
<include refid="select"/>
<where>
<foreach collection="list" open="id IN (" close=")" item="id" separator=",">
#{id}
</foreach>
</where>
</select>
</mapper>
动态 SQL 小结
- 动态 SQL 指的就是 SQL 语句可以根据条件或者参数的不同进行动态的变化。
-
<where>:条件标签 自定生成where关键字 自动去掉多余的and
-
<if>:条件判断的标签。
-
<foreach>:循环遍历的标签。
-
<sql>:抽取SQL 片段的标签。
-
<include>:引入SQL 片段的标签。
MyBatis 核心配置文件 – 分页插件
分页插件介绍
- 分页可以将很多条结果进行分页显示。
- 如果当前在第一页,则没有上一页。如果当前在最后一页,则没有下一页。
- 需要明确当前是第几页,这一页中显示多少条结果。
- 在企业级开发中,分页也是一种常见的技术。而目前使用的MyBatis 是不带分页功能的,如果想实现分页的 功能,需要我们手动编写LIMIT 语句。但是不同的数据库实现分页的SQL 语句也是不同的,所以手写分页 成本较高。这个时候就可以借助分页插件来帮助我们实现分页功能。
- PageHelper:第三方分页助手。将复杂的分页操作进行封装,从而让分页功能变得非常简单。
分页插件实现步骤
- 导入 jar 包。
- 在核心配置文件中集成分页助手插件。
- 在测试类中使用分页助手相关API 实现分页功能。
分页插件代码:
public class Test01 {
@Test
public void selectPaging() throws Exception{
//1.加载核心配置文件
InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml");
//2.获取SqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//3.通过工厂对象获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession(true);
//4.获取StudentMapper接口的实现类对象
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
//通过分页助手来实现分页功能
// 第一页:显示3条数据
//PageHelper.startPage(1,3);
// 第二页:显示3条数据
//PageHelper.startPage(2,3);
// 第三页:显示3条数据
PageHelper.startPage(3,3);
//5.调用实现类的方法,接收结果
List<Student> list = mapper.selectAll();
//6.处理结果
for (Student student : list) {
System.out.println(student);
}
//获取分页相关参数
PageInfo<Student> info = new PageInfo<>(list);
System.out.println("总条数:" + info.getTotal());
System.out.println("总页数:" + info.getPages());
System.out.println("当前页:" + info.getPageNum());
System.out.println("每页显示条数:" + info.getPageSize());
System.out.println("上一页:" + info.getPrePage());
System.out.println("下一页:" + info.getNextPage());
System.out.println("是否是第一页:" + info.isIsFirstPage());
System.out.println("是否是最后一页:" + info.isIsLastPage());
//7.释放资源
sqlSession.close();
is.close();
}
}
分页插件相关参数
- PageInfo:封装分页相关参数的功能类。
- 核心方法
分页插件小结
- 分页:可以将很多条结果进行分页显示。
- 分页插件 jar 包:pagehelper-5.1.10.jar jsqlparser-3.1.jar
-
<plugins>:集成插件标签。
- 分页助手相关 API
1.PageHelper:分页助手功能类。
startPage():设置分页参数
2.PageInfo:分页相关参数功能类。
getTotal():获取总条数
getPages():获取总页数
getPageNum():获取当前页
getPageSize():获取每页显示条数
getPrePage():获取上一页
getNextPage():获取下一页
isIsFirstPage():获取是否是第一页
isIsLastPage():获取是否是最后一页
MyBatis 多表操作
多表模型
- 我们之前学习的都是基于单表操作的,而实际开发中,随着业务难度的加深,肯定需要多表操作的。
- 多表模型分类
一对一:在任意一方建立外键,关联对方的主键。
一对多:在多的一方建立外键,关联一的一方的主键。
多对多:借助中间表,中间表至少两个字段,分别关联两张表的主键。
一对一
- 一对一模型:人和身份证,一个人只有一个身份证。
-
<resultMap>:配置字段和对象属性的映射关系标签。 id 属性:唯一标识 type 属性:实体对象类型
-
<id>:配置主键映射关系标签。
-
<result>:配置非主键映射关系标签。 column 属性:表中字段名称 property 属性: 实体对象变量名称
-
<association>:配置被包含对象的映射关系标签。 property 属性:被包含对象的变量名 javaType 属性:被包含对象的数据类型
一对一映射配置文件代码:
<mapper namespace="com.itheima.table01.OneToOneMapper">
<!--配置字段和实体对象属性的映射关系-->
<resultMap id="oneToOne" type="card">
<id column="cid" property="id" />
<result column="number" property="number" />
<association property="p" javaType="person">
<id column="pid" property="id" />
<result column="name" property="name" />
<result column="age" property="age" />
</association>
</resultMap>
<select id="selectAll" resultMap="oneToOne">
SELECT c.id cid,number,pid,NAME,age FROM card c,person p WHERE c.pid=p.id
</select>
</mapper>
一对多
- 一对多模型:班级和学生,一个班级可以有多个学生。
-
<resultMap>:配置字段和对象属性的映射关系标签。 id 属性:唯一标识 type 属性:实体对象类型
-
<id>:配置主键映射关系标签。
-
<result>:配置非主键映射关系标签。 column 属性:表中字段名称 property 属性: 实体对象变量名称
-
<collection>:配置被包含集合对象的映射关系标签。 property 属性:被包含集合对象的变量名 ofType 属性:集合中保存的对象数据类型
一对多配置文件代码:
<mapper namespace="com.itheima.table02.OneToManyMapper">
<resultMap id="oneToMany" type="classes">
<id column="cid" property="id"/>
<result column="cname" property="name"/>
<!--
collection:配置被包含的集合对象映射关系
property:被包含对象的变量名
ofType:被包含对象的实际数据类型
-->
<collection property="students" ofType="student">
<id column="sid" property="id"/>
<result column="sname" property="name"/>
<result column="sage" property="age"/>
</collection>
<select id="selectAll" resultMap="oneToMany">
SELECT c.id cid,c.name cname,s.id sid,s.name sname,s.age sage FROM classes c,student s WHERE c.id=s.cid
</select>
</resultMap>
</mapper>
多对多
- 多对多模型:学生和课程,一个学生可以选择多门课程、一个课程也可以被多个学生所选择。
-
<resultMap>:配置字段和对象属性的映射关系标签。 id 属性:唯一标识 type 属性:实体对象类型
-
<id>:配置主键映射关系标签。
-
<result>:配置非主键映射关系标签。 column 属性:表中字段名称 property 属性: 实体对象变量名称
-
<collection>:配置被包含集合对象的映射关系标签。 property 属性:被包含集合对象的变量名 ofType 属性:集合中保存的对象数据类型
多对多配置文件代码:
<mapper namespace="com.itheima.table03.ManyToManyMapper">
<resultMap id="manyToMany" type="student">
<id column="sid" property="id"/>
<result column="sname" property="name"/>
<result column="sage" property="age"/>
<collection property="courses" ofType="course">
<id column="cid" property="id"/>
<result column="cname" property="name"/>
</collection>
</resultMap>
<select id="selectAll" resultMap="manyToMany">
SELECT sc.sid,s.name sname,s.age sage,sc.cid,c.name cname FROM student s,course c,stu_cr sc WHERE sc.sid=s.id AND sc.cid=c.id
</select>
</mapper>
多表操作小结
- 多表模型分类:一对一、一对多、多对多。
-
<resultMap>:配置字段和对象属性的映射关系标签。 id 属性:唯一标识 type 属性:实体对象类型
-
<id>:配置主键映射关系标签。
-
<result>:配置非主键映射关系标签。 column 属性:表中字段名称 property 属性: 实体对象变量名称
-
<association>:配置被包含对象的映射关系标签。 property 属性:被包含对象的变量名 javaType 属性:被包含对象的数据类型
-
<collection>:配置被包含集合对象的映射关系标签。 property 属性:被包含集合对象的变量名 ofType 属性:集合中保存的对象数据类型