目录
什么是框架?
什么是ORM框架?
什么是MyBatis?
MyBatis的核心对象
SqlSessionFactoryBuilder对象
SqlSessionFactory对象
SqlSession对象
Mapper代理对象
MyBatis的工作流程
入门案例
环境搭建
MyBatis框架搭建的步骤
创建持久层接口和映射文件
测试持久层接口方法
重点:映射文件注意事项
MyBatis增删改查案例
新增
删除
修改
查询
代码编写流程
注意:优化测试类
MyBatis如何绑定实体类的参数
1、使用${}绑定参数
2、使用#{}绑定参数
3、使用bind标签定义参数
${}和#{}绑定参数的区别
如果方法有多个参数如何绑定
1、顺序传参
2、@Param传参
3、POJO传参
4、Map传参
ParameterType标签
MyBatis核心配置文件SqlMapConfig中的标签
结构
properties标签
settings标签
plugin标签
typeAliases标签
environment标签
transactionManager标签
dataSource标签
mapper标签
1、使用相对路径注册映射文件
2、使用绝对路径注册映射文件
3、注册持久层接口
4、注册一个包下的所有持久层接口
MyBatis映射文件标签
当POJO属性名和数据库列名不一致时
Sql语句的查询字段起与POJO属性相同的别名
自定义映射文件Result
特殊字符处理
动态sql
if标签
where标签
set标签
choose标签和when标签和otherwise标签
foreach标签
foreach标签遍历数组
foreach标签遍历Collection
foreach标签遍历Map
缓存
一级缓存
清空一级缓存
二级缓存
开启二级缓存
测试二级缓存
关联查询
association标签
collection标签
一对一关联查询
一对多关联查询
多对多关联查询
分解式查询
一对多
select属性和column属性
延迟加载
注解开发
环境搭配
增删改查注解
主键回填注解@SelectKey
动态sql
自定义映射@Result
开启二级缓存@CacheNamespace
一对一关联查询
一对多查询
注解和映射文件对比
分页插件PageHelper
MyBatisGenerator工具
在pom文件中配置MBG插件
编写generatorConfig配置文件
运行插件
增删改方法
查询方法
复杂查询
声明部分图片来自百战尚学堂
框架即一个半成品软件。开发者从头开发一个软件需要花费大量精力,于是有一些项目组开发出半成品软件,开发者在这些软件的基础上进行开发,这样的软件就称之为框架。
如果将开发完成的软件比作是一套已经装修完毕的新房,框架就好比是一套已经修建好的毛坯房。用户直接购买毛坯房,保证建筑质量和户型合理的同时可以进行风格的自由装修。
使用框架开发的好处:
使用框架就好比和世界上最优秀的软件工程师共同完成一个项目,并且他们完成的还是基础、全局的工作。
学过JDBC的同学都知道ORM映射吧?ORM映射全程为Object Relation Mapping(对象关系映射),即在数据库和对象之间作映射处理,将查询出的一条数据包装为一个对象。
我们前面说过JDBC有ORM映射思想,但是JDBC是需要手动进行ORM映射的,ORM映射并不是什么难的代码逻辑,但是在处理业务中常常会用到ORM映射,所以MyBatis的作用就是帮助我们完成ORM映射,不需要我们手动打代码,只需要配置一些配置文件即可
SqlSession工厂构建者对象,使用构造者模式创建SqlSession工厂对象。
SqlSession工厂,使用工厂模式创建SqlSession对象。
该对象可以操作数据库,也可以使用动态代理模式创建持久层接口的代理对象操作数据库。
持久层接口的代理对象,他具体实现了持久层接口,用来操作数据库。
1、创建maven工程并在pom.xml中引入依赖,需要引入mybatis的依赖
2、创建myBatis的核心配置文件SqlMapConfig.xml
3. 将log4j.properties文件放入resources中,让控制台打印SQL语句。
4. 创建实体类(实体类的名字建议和表名相同,且实体类的属性和表的列名相同)
1. 创建maven工程,引入依赖
org.mybatis
mybatis
3.5.7
mysql
mysql-connector-java
8.0.26
junit
junit
4.10
log4j
log4j
1.2.12
2. 创建mybatis核心配置文件SqlMapConfig.xml
3. 将log4j.properties文件放入resources中,让控制台打印SQL语句。
4. 创建实体类
首先我们需要有一张表进行测试,我们这里使用user表进行测试
//1. 在java目录创建持久层接口
public interface UserMapper{
//当使用MyBatis调用该方法时,
//MyBatis会自动根据sql语句查询并封装为User对象放到该容器中
ListfindAll();
}
2. 在resource目录创建映射文件
3. 将映射文件配置到mybatis核心配置文件中(SqlMapConfig.xml)
public class TestUserMapper {
@Test
public void testFindAll() throws IOException {
//读取核心配置文件
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//SqlSessionFactoryBuilder对象创建SqlSessionFactory对象
SqlSessionFactory factory = builder.build(is);
//SqlSessionFactory对象创建SqlSession对象
SqlSession session = factory.openSession();
//获取代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
//调用方法
List list = userMapper.findAll();
list.forEach(System.out::println);
}
}
持久层接口和映射文件都创建出来后,那么我们来测试增删改查,代码的编写步骤是:
1、持久层接口添加方法
2、映射文件中添加对应标签
3、测试
1、持久层添加方法
2、映射文件中添加对应标签,因为是新增,所以需要添加insert标签
id属性时持久层接口的方法名,parameterType是参数类型,必须写参数的全类名
@Test
public void testAdd() throws IOException {
//读取核心配置文件
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//SqlSessionFactoryBuilder对象创建SqlSessionFactory对象
SqlSessionFactory factory = builder.build(is);
//SqlSessionFactory对象创建SqlSession对象
SqlSession session = factory.openSession();
//获取代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
//调用方法
User user = new User("zhangsan","男","上海");
userMapper.add(user);
//提交事务
session.commit();
}
public interface UserMapper {
//查询所有
List findAll();
//新增
void add(User user);
//删除
void delete(int id);
}
delete from user where id = #{id}
@Test
public void testDelete() throws IOException {
//读取核心配置文件
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//SqlSessionFactoryBuilder对象创建SqlSessionFactory对象
SqlSessionFactory factory = builder.build(is);
//SqlSessionFactory对象创建SqlSession对象
SqlSession session = factory.openSession();
//获取代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
//调用方法
userMapper.delete(18);
//提交事务
session.commit();
}
public interface UserMapper {
//查询所有
List findAll();
//新增
void add(User user);
//删除
void delete(int id);
//修改
void update(User user);
}
update user set username1 = #{username1},sex1 = #{sex1},address1 = #{address1}
@Test
public void testUpdate() throws IOException {
//读取核心配置文件
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//SqlSessionFactoryBuilder对象创建SqlSessionFactory对象
SqlSessionFactory factory = builder.build(is);
//SqlSessionFactory对象创建SqlSession对象
SqlSession session = factory.openSession();
//获取代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
//调用方法
User user = new User("一拳超人","男","网络");
userMapper.update(user);
//提交事务
session.commit();
}
public interface UserMapper {
//查询所有
List findAll();
//新增
void add(User user);
//删除
void delete(int id);
//修改
void update(User user);
//根据id查询
User findById(int id);
}
@Test
public void testFindById() throws IOException {
//读取核心配置文件
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//SqlSessionFactoryBuilder对象创建SqlSessionFactory对象
SqlSessionFactory factory = builder.build(is);
//SqlSessionFactory对象创建SqlSession对象
SqlSession session = factory.openSession();
//获取代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
//调用方法
User user = userMapper.findById(1);
System.out.println(user);
}
写到这里大家有没有发现,我们每次测试的时候,都要创建SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession对象、代理对象,每次都要重复写一样的代码,这样使得代码非常冗余,那么有什么方法可以使代码简化呢?
利用Junit的前置后置方法@Before和@After注解简化代码,优化测试类代码
优化过后我们会就可以将以前代码中的这些对象删除了,应为junit在启动的时候会自动调用@Before修饰的方法,清理了过后我们会发现整段代码都干净了很多
public class TestUserMapper {
InputStream is = null;
SqlSession session = null;
UserMapper userMapper = null;
@Before
public void before() throws IOException {
//读取核心配置文件
is = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//SqlSessionFactoryBuilder对象创建SqlSessionFactory对象
SqlSessionFactory factory = builder.build(is);
//SqlSessionFactory对象创建SqlSession对象
session = factory.openSession();
//获取代理对象
userMapper = session.getMapper(UserMapper.class);
}
@After
public void after() throws IOException {
//释放资源
session.close();
is.close();
}
@Test
public void testFindAll() throws IOException {
//调用方法
List list = userMapper.findAll();
list.forEach(System.out::println);
}
@Test
public void testAdd() throws IOException {
//调用方法
User user = new User("zhangsan","男","上海");
userMapper.add(user);
//提交事务
session.commit();
}
@Test
public void testDelete() throws IOException {
//调用方法
userMapper.delete(18);
//提交事务
session.commit();
}
@Test
public void testUpdate() throws IOException {
//调用方法
User user = new User("一拳超人","男","网络");
userMapper.update(user);
//提交事务
session.commit();
}
@Test
public void testFindById() throws IOException {
//调用方法
User user = userMapper.findById(1);
System.out.println(user);
}
}
${}绑定参数相当于JDBC里边的Statement对象,是通过字符串拼接实现的绑定,所以${}绑定参数会出现sql注入的问题
//测试方法写法如下:
@Test
public void testFindByNameLike(){
List users = userMapper.findByUsernameLike("尚学堂");
users.forEach(System.out::println);
}
根据log4j输出的加载信息可以看到,条件是直接拼接到sql语句中的,所以这就会出现sql注入的问题
#{}绑定参数相当于JDBC里的PreparedStatement对象,PreparedStatement对象是通过预编译,然后绑定参数的方式实现避免sql注入
@Test
public void testFindByNameLike(){
List users = userMapper.findByUsernameLike("%尚学堂%");
users.forEach(System.out::println);
}
如果使用#还不想在调用方法的参数中添加%,可以使用
其实也就相当于PreparedStatement和Statement的区别
Sql中的参数使用arg0,arg1...或param1,param2...表示参数的顺序。此方法可读性较低,在开发中不建议使用。
1、持久层接口方法
/**
* 分页查询
* @param startIndex 开始索引
* @param pageSize 每页条数
* @return
*/
List findPage(int startIndex,int pageSize);
2、映射文件
3、测试
@Test
public void testFindPage(){
List users = userMapper.findPage(0,3);
users.forEach(System.out::println);
}
1、持久层接口方法
/**
* 分页查询
* @param startIndex 开始索引
* @param pageSize 每页条数
* @return
*/
List findPage(int startIndex,int pageSize);
2、映射文件
3、测试
@Test
public void testFindPage(){
List users = userMapper.findPage(0,3);
users.forEach(System.out::println);
}
自定义POJO类,该类的属性就是要传递的参数,
在SQL语句中绑定参数时使用POJO的属性名作为参数名即可。此方式推荐使用。
1、持久层接口创建方法
List findPage2(PageQuery pageQuery);
2、自定义POJO类,给类的属性就是要传递的参数
public class PageQuery {
private int startIndex;
private int pageSize;
public PageQuery(int pageNum, int pageSize) {
this.startIndex = pageNum;
this.pageSize = pageSize;
}
public int getStartIndex() {
return startIndex;
}
public void setStartIndex(int startIndex) {
this.startIndex = startIndex;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
@Override
public String toString() {
return "PageQuery{" +
"pageNum=" + startIndex +
", pageSize=" + pageSize +
'}';
}
}
3、映射对象添加对应标签
4、测试
@Test
public void testFindPage2(){
PageQuery pageQuery=new PageQuery(3, 3);
List users = userMapper.findPage2(pageQuery);
users.forEach(System.out::println);
}
如果不想自定义POJO,可以使用Map作为传递参数的载体,在SQL语句中绑定参数时使用Map的Key作为参数名即可。此方法推荐使用。
1、持久层接口添加方法
List findPage3(Map params);
2、映射文件中添加对应标签
3、测试
@Test
public void testFindPage3(){
Map params = new HashMap();
params.put("startIndex",0);
params.put("pageSize",4);
List users = userMapper.findPage3(params);
users.forEach(System.out::println);
}
可能现在还有童鞋不知道ParameterType标签是干什么用的,其实前面的映射文件注意事项里讲过,ParameterType标签就是用来绑定参数的,但是它只能绑定一个参数,也就是说如果方法有多个参数就只能通过上面的四种方式任意一种绑定
-configuration
-properties(属性)
-property
-settings(全局配置参数)
-setting
-plugins(插件)
-plugin
-typeAliases(别名)
-typeAliase
-package
-environments(环境)
-environment
-transactionManager(事务管理)
-dataSource(数据源)
-mappers(映射器)
-mapper
-package
属性值定义。properties标签中可以定义属性值,也可以引入外部配置文件。无论是内部定义还是外部引入,都可以使用${name}获取值。
例如:我们可以将数据源配置写到外部的db.properties中,再使用properties标签引入外部配置文件,这样可以做到动态配置数据源。
作用:该标签的作用就是用来给类起别名的
1、为一个类配置别名
为一个类配置别名
此时我们即可在映射文件中使用自定义别名,如:
1. 配置文件:
2. 映射文件:
2、为一个所有包下的所有类配置别名
此时该包下的所有类都有了别名,别名代替了包名。如:
3. 配置文件:
4. 映射文件:
作用:控制是否做事务处理
连接池
dataSource的type属性:
使用mapper标签的resource属性实现使用相对路径注册映射文件
使用mapper标签的url属性实现使用绝对路径注册映射文件
使用mapper标签的class属性实现注册持久层接口
使用package标签的name属性实现注册一个包下的所有持久层接口
Result标签的作用:自定义映射关系。
MyBatis可以将数据库结果集封装到对象中,是因为结果集的列名和对象属性名相同:
当POJO属性名和数据库列名不一致时,MyBatis无法自动完成映射关系。如:
此时有两种解决方案:
• 在映射文件中,使用自定义映射关系:
• 在
在Mybatis映射文件中尽量不要使用一些特殊字符,如:<,>等。
我们可以使用符号的实体来表示:
符号 |
实体 |
< |
< |
> |
> |
& |
& |
‘ |
' |
“ |
" |
一个查询的方法的Sql语句不一定是固定的。比如电商网站的查询商品,用户使用不同条件查询,Sql语句就会添加不同的查询条件。此时就需要在方法中使用动态Sql语句。
用法为:
1、持久层接口创建方法
// 用户通用查询
List findByCondition(User user);
2、映射文件添加对应标签
select * from user where 1=1
and username1 like #{username1}
and sex1 = #{sex1}
and address1 = #{address1}
3、测试
@Test
public void testFindByCondition(){
User user = new User("%尚学堂%","男");
List users = userMapper.findByCondition(user);
users.forEach(System.out::println);
}
更符合程序员的开发习惯,使用
我们通过上面if标签的findByCondition方法修改一下映射文件中对应的标签进行测试
select * from user
username1 like #{username1}
and sex1 = #{sex1}
1、持久层创建方法
//修改
void update(User user);
2、映射文件对应标签
update user
username1 = #{username1},
sex1 = #{sex1},
id = #{id}
3、测试
@Test
public void testUpdate() throws IOException {
//调用方法
User user = new User(16,"浩克");
userMapper.update(user);
//提交事务
session.commit();
}
这些标签表示多条件分支,类似JAVA中的switch...case。
1、持久层接口创建方法
// 用户通用查询2
List findByCondition2(User user);
2、映射文件创建对应标签
select * from user
username1 like #{likeName}
username1 = #{username1}
id = 1
1、用户名<5模糊查询
@Test
public void testFindByCondition2(){
User user = new User("尚学堂");
List users = userMapper.findByCondition2(user);
users.forEach(System.out::println);
}
2、5<=用户名<10 精准查询
@Test
public void testFindByCondition2(){
User user = new User("尚学堂666");
List users = userMapper.findByCondition2(user);
users.forEach(System.out::println);
}
3、用户名>=10 直接查询id=1的数据
@Test
public void testFindByCondition2(){
User user = new User("尚学堂66666666");
List users = userMapper.findByCondition2(user);
users.forEach(System.out::println);
}
我们使用
1、持久层接口创建方法
//批量删除
void deleteBatch(int[] ids);
2、映射文件添加对应标签
delete from user
#{id}
3、测试
@Test
public void testDeleteBatch(){
int[] ids = new int[]{16,19,20};
userMapper.deleteBatch(ids);
//提交事务
session.commit();
}
删除前
删除后
1、持久层添加方法
//批量添加
void insertBatch(List users);
2、映射文件添加对应标签
insert into user
(#{user.id},#{user.username1},#{user.sex1},#{user.address1})
3、测试
@Test
public void testInsertBatch(){
User user = new User("一拳超人","男","网络");
User user1 = new User("路飞","男","海贼王");
List list = new ArrayList<>();
list.add(user);
list.add(user1);
//添加一批数据
userMapper.insertBatch(list);
//事务提交
session.commit();
}
添加数据前
我们使用
1、持久层添加方法
/**
* 多条件查询
* @param map 查询的条件键值对 键:属性名 值:属性值
* @return
*/
List findUser(@Param("queryMap") Mapmap);
2、映射文件添加对应标签
select * from user
${key} = #{value}
3、测试
@Test
public void testFindUser(){
Map queryMap = new HashMap();
queryMap.put("sex1","男");
queryMap.put("address1","北京");
List users = userMapper.findUser(queryMap);
users.forEach(System.out::println);
}
缓存是内存当中一块存储数据的区域,目的是提高查询效率。MyBatis会将查询结果存储在缓存当中,当下次执行相同的SQL时不访问数据库,而是直接从缓存中获取结果,从而减少服务器的压力。
进行以下操作可以清空MyBatis一级缓存:
开启二级缓存需要两步:
①MyBatis配置文件添加如下设置:
②在映射文件添加
如果查询到的集合中对象过多,二级缓存只能缓存1024个对象引用。可以通过
1、持久层创建方法
//根据id查询
User findById(int id);
2、映射文件添加对应标签
select * from user where id = #{id}
3、测试
@Test
public void testFindById() throws IOException {
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
SqlSession session1 = factory.openSession();
SqlSession session2 = factory.openSession();
UserMapper mapper1 = session1.getMapper(UserMapper.class);
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User user1 = mapper1.findById(1);
System.out.println(user1);
session1.close();
System.out.println("--------------------------");
User user2 = mapper2.findById(1);
System.out.println(user2);
}
通过结果我们可以发现,mybatis只和数据库交互了一次,第二次是直接在缓存中获取的
MyBatis的关联查询分为一对一关联查询和一对多关联查询。
mybatis进行多表查询时会用上association标签,它的属性包括property,column,javaType等,它的作用是让实体类对象与数据库表的列相互对应,以便让mybatis可以进行多表查询。
使用的场景:association的使用场景为1:1的情况。
作用:在Mybaits中collection标签是用来实现连表查询的。
使用的场景:collection的使用场景为1:n和n:n两种情况。
添加的内容:使用collection的时候需要在类中添加关联集合(查询哪个类就在哪个类中添加)。
所以在关联查询之前我们需要有几张有关联的表,并且创建对应的POJO类
import java.util.List;
public class Classes {
private Integer cid;
private String className;
private List studentList;
public Classes(Integer cid, String className, List studentList) {
this.cid = cid;
this.className = className;
this.studentList = studentList;
}
public Integer getCid() {
return cid;
}
public void setCid(Integer cid) {
this.cid = cid;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public List getStudentList() {
return studentList;
}
public void setStudentList(List studentList) {
this.studentList = studentList;
}
@Override
public String toString() {
return "Classes{" +
"cid=" + cid +
", className='" + className + '\'' +
", studentList=" + studentList +
'}';
}
}
public class Student {
private Integer sid;
private String name;
private int age;
private String sex;
private Classes classes;
public Student(Integer sid, String name, int age, String sex, Classes classes) {
this.sid = sid;
this.name = name;
this.age = age;
this.sex = sex;
this.classes = classes;
}
public Integer getSid() {
return sid;
}
public void setSid(Integer sid) {
this.sid = sid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Classes getClasses() {
return classes;
}
public void setClasses(Classes classes) {
this.classes = classes;
}
@Override
public String toString() {
return "Student{" +
"sid=" + sid +
", name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
", classes=" + classes +
'}';
}
}
教室对于学生来说就是一对一的,因为一个学生只能在一个教室,所以我们来关联查询学生,当查出学生时把学生的教室一起查出来
1、持久层创建方法
import java.util.List;
public interface StudentMapper {
//查找所有学生
List findAll();
}
2、映射文件添加对应标签
select * from student left join classes on student.classId = classes.cid
3、测试
public class TestStudentMapper {
InputStream is = null;
SqlSession session = null;
StudentMapper studentMapper = null;
@Before
public void before() throws IOException {
//读取核心配置文件
is = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//SqlSessionFactoryBuilder对象创建SqlSessionFactory对象
SqlSessionFactory factory = builder.build(is);
//SqlSessionFactory对象创建SqlSession对象
session = factory.openSession();
//获取代理对象
studentMapper = session.getMapper(StudentMapper.class);
}
@After
public void after() throws IOException {
//释放资源
session.close();
is.close();
}
@Test
public void testFindAll(){
List list = studentMapper.findAll();
list.forEach(System.out::println);
}
}
学生对于教室来说就是一对多的关系,一个教室可以有多个学生,所以我们查询教室顺便把这个教室的所有学生查询出来
1、持久层接口创建方法
//查询所有教室
List findAll();
2、映射文件添加对应标签
select * from classes left join student on classes.cid = student.classId
3、测试
public class TestClassesMapper {
InputStream is = null;
SqlSession session = null;
ClassesMapper classesMapper = null;
@Before
public void before() throws IOException {
//读取核心配置文件
is = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//SqlSessionFactoryBuilder对象创建SqlSessionFactory对象
SqlSessionFactory factory = builder.build(is);
//SqlSessionFactory对象创建SqlSession对象
session = factory.openSession();
//获取代理对象
classesMapper = session.getMapper(ClassesMapper.class);
}
@After
public void after() throws IOException {
//释放资源
session.close();
is.close();
}
@Test
public void testFindAll(){
List list = classesMapper.findAll();
list.forEach(System.out::println);
}
}
MyBatis多对多关联查询本质就是两个一对多关联查询。
例如有老师类和班级类:
一个老师对应多个班级,也就是老师类中有一个班级集合属性。
一个班级对应多个老师,也就是班级类中有一个老师集合属性。
所以我们需要有三张表,老师表、教室表和中间表,并且创建对应的POJO类
public class Teacher {
private Integer tid;
private String tname;
private List classesList;
public Teacher(Integer tid, String tname, List classesList) {
this.tid = tid;
this.tname = tname;
this.classesList = classesList;
}
public Teacher() {
}
public Integer getTid() {
return tid;
}
public void setTid(Integer tid) {
this.tid = tid;
}
public String getTname() {
return tname;
}
public void setTname(String tname) {
this.tname = tname;
}
public List getClassesList() {
return classesList;
}
public void setClassesList(List classesList) {
this.classesList = classesList;
}
@Override
public String toString() {
return "Teacher{" +
"tid=" + tid +
", tname='" + tname + '\'' +
", classesList=" + classesList +
'}';
}
}
public class Classes {
private Integer cid;
private String className;
private List studentList;
private List teacherList;
public Classes(Integer cid, String className, List studentList, List teacherList) {
this.cid = cid;
this.className = className;
this.studentList = studentList;
this.teacherList = teacherList;
}
public Classes() {
}
public Integer getCid() {
return cid;
}
public void setCid(Integer cid) {
this.cid = cid;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public List getStudentList() {
return studentList;
}
public void setStudentList(List studentList) {
this.studentList = studentList;
}
public List getTeacherList() {
return teacherList;
}
public void setTeacherList(List teacherList) {
this.teacherList = teacherList;
}
@Override
public String toString() {
return "Classes{" +
"cid=" + cid +
", className='" + className + '\'' +
", studentList=" + studentList +
", teacherList=" + teacherList +
'}';
}
}
1、持久层创建方法
public interface TeacherMapper {
//查询所有老师
List findAll();
}
2、映射文件添加对应标签
select * from teacher
left join classes_teacher
on teacher.tid = classes_teacher.tid
left join classes
on classes_teacher.cid = classes.cid
3、测试
public class TestTeacherMapper {
InputStream is = null;
SqlSession session = null;
TeacherMapper teacherMapper = null;
@Before
public void before() throws IOException {
//读取核心配置文件
is = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//SqlSessionFactoryBuilder对象创建SqlSessionFactory对象
SqlSessionFactory factory = builder.build(is);
//SqlSessionFactory对象创建SqlSession对象
session = factory.openSession();
//获取代理对象
teacherMapper = session.getMapper(TeacherMapper.class);
}
@After
public void after() throws IOException {
//释放资源
session.close();
is.close();
}
@Test
public void testFindAll(){
List list = teacherMapper.findAll();
list.forEach(System.out::println);
}
}
在MyBatis多表查询中,使用连接查询时一个Sql语句就可以查询出所有的数据。如:
# 查询班级时关联查询出学生
select *
from classes
left join student
on student.classId = classes.cid
也可以使用分解式查询,即将一个连接Sql语句分解为多条Sql语句,如:
# 查询班级时关联查询出学生
select * from classes;
select * from student where classId =1;
select * from student where classId =2;
这种写法也叫N+1查询。
连接查询:
N+1查询:
分解式查询需要两个方法,以查询教室里的所有学生为例
Classes实体类里需要有findAll方法,这是我们要调用的方法
Student类里需要有根据关联列查询学生的方法,这是映射文件要调用的方法
1、持久层接口创建方法
public interface ClassesMapper {
//查询所有教室
List findAll();
}
public interface StudentMapper {
//查找所有学生
List findAll();
//根据classId查找学生
Student findByCid(int classId);
}
2、映射文件添加对应标签
ClassesMapper.xml
select * from classes
StudentMapper.xml
select * from student where classId = #{cid}
3、测试
public class TestClassesMapper {
InputStream is = null;
SqlSession session = null;
ClassesMapper classesMapper = null;
@Before
public void before() throws IOException {
//读取核心配置文件
is = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//SqlSessionFactoryBuilder对象创建SqlSessionFactory对象
SqlSessionFactory factory = builder.build(is);
//SqlSessionFactory对象创建SqlSession对象
session = factory.openSession();
//获取代理对象
classesMapper = session.getMapper(ClassesMapper.class);
}
@After
public void after() throws IOException {
//释放资源
session.close();
is.close();
}
@Test
public void testFindAll(){
List list = classesMapper.findAll();
list.forEach(System.out::println);
}
}
select:从表查询调用的方法
column:调用方法时传入的参数字段
分解式查询又分为两种加载方式:
延迟加载在获取关联数据时速度较慢,但可以节约资源,即用即取。
开启延迟加载
一般情况下,一对多查询使用延迟加载,一对一查询使用立即加载。
MyBatis可以使用注解替代映射文件。映射文件的作用就是定义Sql语句,可以在持久层接口上使用@Select/@Delete/@Insert/@Update定义Sql语句,这样就不需要使用映射文件了。
public interface UserMapper {
@Select("select * from user")
List findAll();
}
public class TestUserMapper {
InputStream is = null;
SqlSession session = null;
UserMapper userMapper = null;
@Before
public void before() throws IOException {
//读取核心配置文件
is = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//SqlSessionFactoryBuilder对象创建SqlSessionFactory对象
SqlSessionFactory factory = builder.build(is);
//SqlSessionFactory对象创建SqlSession对象
session = factory.openSession();
//获取代理对象
userMapper = session.getMapper(UserMapper.class);
}
@After
public void after() throws IOException {
//释放资源
session.close();
is.close();
}
@Test
public void testFindAll() throws IOException {
//调用方法
List list = userMapper.findAll();
list.forEach(System.out::println);
}
}
public interface UserMapper {
@Select("select * from user")
List findAll();
//新增
@Insert("insert into user values(id=#{id},username1 = #{username1},sex1=#{sex1},address1=#{address1})")
void insert(User user);
@Test
public void testInsert() throws IOException {
User user = new User("索隆","男","海贼王");
userMapper.insert(user);
System.out.println(user);
//事务提交
session.commit();
}
当某个键为主键且为自动增长时,可以使用@SelectKey注解回填
@SelectKey
(keyColumn = "id", keyProperty = "id", resultType = int.class,before = false, statement = "SELECT LAST_INSERT_ID()")
keyProperty:主键属性名,
keyColumn:主键列名,
resultType:主键类型,
statement:调用的方法
//新增
@SelectKey(keyProperty = "id",keyColumn = "id",resultType = int.class,statement = "SELECT LAST_INSERT_ID()", before = false)
@Insert("insert into user(username1,sex1,address1) values(#{username1},#{sex1},#{address1})")
void insert(User user);
@Test
public void testInsert() throws IOException {
User user = new User("娜美","女","海贼王");
userMapper.insert(user);
System.out.println(user);
//事务提交
session.commit();
}
此时我们并没有给user设置id,但是通过该控制台我们可以发现mybatis插入数据后,主键回填了
1、使用脚本标签
将Sql嵌套在内即可使用动态Sql标签
2、在方法中构建动态Sql
在MyBatis中有@SelectProvider、@UpdateProvider、@DeleteProvider、@InsertProvider注解。当使用这些注解时将不在注解中直接编写SQL,而是调用某个类的方法来生成SQL。
当POJO属性名与数据库列名不一致时,需要自定义实体类和结果集的映射关系,在MyBatis注解开发中,
使用@Results定义并使用自定义映射,
使用@ResultMap使用自定义映射,用法如下:
1、持久层创建方法并添加对应标签
@Results(id="userDiyMapper",value={
@Result(id=true,property = "id",column = "id"),
@Result(property = "username1",column = "username1"),
@Result(property = "sex1",column = "sex1"),
@Result(property = "address1",column = "address1"),
})
@Select("select * from user")
List findAll();
@Test
public void testFindAll() throws IOException {
//调用方法
List list = userMapper.findAll();
list.forEach(System.out::println);
}
MyBatis默认开启一级缓存,接下来我们学习如何在注解开发时使用二级缓存:
在MyBatis的注解开发中对于多表查询只支持分解查询,不支持连接查询。
主表的查询配置自定义映射关系
/**
* property:属性名
* column:调用从表方法时传入的参数列
* one:表示该属性是一个对象
* select:调用的从表方法
* fetchType:加载方式
*/
public interface ClassesMapper {
//根据cid查找班级
Classes findByCid(int cid);
}
public interface StudentMapper {
@Results(id="studentMapper",value={
@Result(id=true,property = "sid",column = "sid"),
@Result(property = "name",column = "name"),
@Result(property = "age",column = "age"),
@Result(property = "sex",column = "sex"),
/**
* property:属性名
* column:调用从表方法时传入的参数列
* one:表示该属性是一个对象
* select:调用的从表方法
* fetchType:加载方式
*/
@Result(
property = "classes",
column = "cid",
one = @One(select = "com.itbaizhan.ClassesMapper.findByCid",
fetchType = FetchType.LAZY))
})
@Select("select * from student")
List findAll();
}
public class TestStudentMapper {
InputStream is = null;
SqlSession session = null;
StudentMapper studentMapper = null;
@Before
public void before() throws IOException {
//读取核心配置文件
is = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//SqlSessionFactoryBuilder对象创建SqlSessionFactory对象
SqlSessionFactory factory = builder.build(is);
//SqlSessionFactory对象创建SqlSession对象
session = factory.openSession();
//获取代理对象
studentMapper = session.getMapper(StudentMapper.class);
}
@After
public void after() throws IOException {
//释放资源
session.close();
is.close();
}
@Test
public void testFindAll(){
List list = studentMapper.findAll();
list.forEach(System.out::println);
}
}
@Results(id="classesMapper",value={
@Result(id = true,property = "cid",column = "cid"),
@Result(property = "className",column = "className"),
@Result(
property = "studentList",
column = "cid",
//many表示该属性是一个集合
many=@Many(select = "com.itbaizhan.mapper.StudentMapper.findByClassId",
fetchType = FetchType.LAZY))
})
//查询所有
@Select("select * from classes")
List findAll();
//根据cid查找学生
@Select("select * from student where classId = #{classId}")
Student findByClassId(int classId);
public class TestClassesMapper {
InputStream is = null;
SqlSession session = null;
ClassesMapper classesMapper = null;
@Before
public void before() throws IOException {
//读取核心配置文件
is = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//SqlSessionFactoryBuilder对象创建SqlSessionFactory对象
SqlSessionFactory factory = builder.build(is);
//SqlSessionFactory对象创建SqlSession对象
session = factory.openSession();
//获取代理对象
classesMapper = session.getMapper(ClassesMapper.class);
}
@After
public void after() throws IOException {
//释放资源
session.close();
is.close();
}
@Test
public void testFindAll(){
List list = classesMapper.findAll();
list.forEach(System.out::println);
}
}
MyBatis中更推荐使用映射文件开发,Spring、SpringBoot更推荐注解方式。具体使用要视项目情况而定。它们的优点对比如下:
映射文件:
注解:
PageHelper是一款非常好用的开源免费的Mybatis第三方分页插件。使用该插件时,只要传入分页参数,即可自动生成页面对象。我们使用该插件分页查询所有用户:
1. 引入依赖
com.github.pagehelper
pagehelper
5.3.0
@Test
public void testFindPage() {
// (1)查询前设置分页参数,参数一:页数,从1开始。参数二:每页条数
PageHelper.startPage(1, 3);
// (2)正常查询
List all = userMapper.findAll();
// (3)创建页面对象,创建时将查询结果传入构造方法
PageInfo pageInfo = new PageInfo(all);
// (4)打印页面对象的属性
System.out.println("结果集:"+pageInfo.getList());
System.out.println("总条数:"+pageInfo.getTotal());
System.out.println("总页数"+pageInfo.getPages());
System.out.println("当前页"+pageInfo.getPageNum());
System.out.println("每页条数"+pageInfo.getSize());
}
MyBatis Generator(MBG)是MyBatis官方提供的代码生成器。它可以根据数据库的表结构自动生成POJO类、持久层接口与映射文件,极大减少了代码的编写量,提高开发效率。
MBG可以作为项目引入使用,也可以作为Maven插件使用,其中作为Maven插件使用更加方便快捷。
org.mybatis.generator
mybatis-generator-maven-plugin
1.3.7
src/main/resources/generatorConfig.xml
true
true
有些同学可能不知道为啥突然就添加了持久层接口,这是因为我们在配置generatorConfig.xml核心配置文件的时候,添加了table标签,该标签就是用来告诉generator应该生成数据库中哪个表的POJO类和映射文件
generator在生成映射文件的时候自动添加了一些简单的增删改查标签,所以我们可以直接使用
public class TestMBG {
InputStream is = null;
SqlSession session = null;
ProductMapper productMapper = null;
@Before
public void before() throws IOException {
is = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
session = factory.openSession();
productMapper = session.getMapper(ProductMapper.class);
}
@After
public void after() throws IOException {
session.close();
is.close();
}
// 新增
@Test
public void testAdd(){
Product product = new Product("百战Python课", 15000.0);
productMapper.insert(product);
session.commit();
}
// 修改
@Test
public void testUpdate(){
Product product = new Product(5,"百战Python课", 25000.0);
productMapper.updateByPrimaryKey(product);
session.commit();
}
// 删除
@Test
public void testDelete(){
productMapper.deleteByPrimaryKey(5);
session.commit();
}
}
generator默认有个根据主键查询,还有一个Example类,该类就是用来设置查询条件的
复杂查询需要通过POJO类的扩展类POJOExample,自定义查询条件,MBG在生成POJO类时会将扩展类一起生成
然后通过扩展类Example的内部类Criteria定义查询条件,如果没有定义查询条件那么默认查询所有
如果是多条件or查询,那么需要创建多个criteria对象,然后通过扩展类Example的or方法将多个criteria对象包含在一起