目录
声明部分图片来自百战尚学堂
什么是框架?
框架即一个半成品软件。开发者从头开发一个软件需要花费大量精力,于是有一些项目组开发出半成品软件,开发者在这些软件的基础上进行开发,这样的软件就称之为框架。
如果将开发完成的软件比作是一套已经装修完毕的新房,框架就好比是一套已经修建好的毛坯房。用户直接购买毛坯房,保证建筑质量和户型合理的同时可以进行风格的自由装修。
使用框架开发的好处:
- 省去大量的代码编写、减少开发时间、降低开发难度。
- 限制程序员必须使用框架规范开发,增强代码的规范性,降低程序员之间沟通及日后维护的成本。
- 将程序员的注意力从技术中抽离出来,更集中在业务层面。
使用框架就好比和世界上最优秀的软件工程师共同完成一个项目,并且他们完成的还是基础、全局的工作。
什么是ORM框架?
学过JDBC的同学都知道ORM映射吧?ORM映射全程为Object Relation Mapping(对象关系映射),即在数据库和对象之间作映射处理,将查询出的一条数据包装为一个对象。
什么是MyBatis?
我们前面说过JDBC有ORM映射思想,但是JDBC是需要手动进行ORM映射的,ORM映射并不是什么难的代码逻辑,但是在处理业务中常常会用到ORM映射,所以MyBatis的作用就是帮助我们完成ORM映射,不需要我们手动打代码,只需要配置一些配置文件即可
MyBatis的核心对象
SqlSessionFactoryBuilder对象
SqlSession工厂构建者对象,使用构造者模式创建SqlSession工厂对象。
SqlSessionFactory对象
SqlSession工厂,使用工厂模式创建SqlSession对象。
SqlSession对象
该对象可以操作数据库,也可以使用动态代理模式创建持久层接口的代理对象操作数据库。
Mapper代理对象
持久层接口的代理对象,他具体实现了持久层接口,用来操作数据库。
MyBatis的工作流程
- 创建SqlSessionFactoryBuilder对象
- 通过SqlSessionFactoryBuilder对象的build方法构建SqlSessionFactory对象:构造者模式
- 通过SqlSessionFactory对象的openSession方法生产SqlSession对象:工厂模式
- SqlSession对象创建了持久层接口的代理对象:动态代理模式
- 代理对象操作数据库
入门案例
环境搭建
1、创建maven工程并在pom.xml中引入依赖,需要引入mybatis的依赖
2、创建myBatis的核心配置文件SqlMapConfig.xml
3. 将log4j.properties文件放入resources中,让控制台打印SQL语句。
4. 创建实体类(实体类的名字建议和表名相同,且实体类的属性和表的列名相同)
1. 创建maven工程,引入依赖
<dependencies>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!-- mysql驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
<!-- log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
</dependencies>
2. 创建mybatis核心配置文件SqlMapConfig.xml
<?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">
<!-- 事务类型 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 数据源 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
</configuration>
3. 将log4j.properties文件放入resources中,让控制台打印SQL语句。
4. 创建实体类
MyBatis框架搭建的步骤
- java项目中创建持久层接口
- resource项目中创建映射文件
- 将映射文件注册到SqlMapConfig.xml文件中
首先我们需要有一张表进行测试,我们这里使用user表进行测试
创建持久层接口和映射文件
//1. 在java目录创建持久层接口
public interface UserMapper{
//当使用MyBatis调用该方法时,
//MyBatis会自动根据sql语句查询并封装为User对象放到该容器中
List<User>findAll();
}
2. 在resource目录创建映射文件
<?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.itbaizhan.mapper.UserMapper">
<select id="findAll" resultType="com.itbaizhan.pojo.User">
select * from user
</select>
</mapper>
3. 将映射文件配置到mybatis核心配置文件中(SqlMapConfig.xml)
<!-- 注册映射文件 -->
<mappers>
<mapper resource="com/itbaizhan/mapper/UserMapper.xml"> </mapper>
</mappers>
测试持久层接口方法
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<User> list = userMapper.findAll();
list.forEach(System.out::println);
}
}
重点:映射文件注意事项
- 映射文件要和接口名称相同。
- 映射文件要和接口的目录结构相同。
- 映射文件中namespace属性要写接口的全名。
- 映射文件中标签的id属性是接口方法的方法名。
- 映射文件中标签的resultType属性是接口方法的返回值类型。
- 映射文件中标签的parameterType属性是接口方法的参数类型。
- 映射文件中resultType、parameterType属性要写全类名,如果是集合类型,则写其泛型的全类名。
MyBatis增删改查案例
持久层接口和映射文件都创建出来后,那么我们来测试增删改查,代码的编写步骤是:
1、持久层接口添加方法
2、映射文件中添加对应标签
3、测试
新增<insert>
1、持久层添加方法
2、映射文件中添加对应标签,因为是新增,所以需要添加insert标签
id属性时持久层接口的方法名,parameterType是参数类型,必须写参数的全类名
3、测试
@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<User> findAll();
//新增
void add(User user);
//删除
void delete(int id);
}
<delete id="delete" parameterType="int">
delete from user where id = #{id}
</delete>
@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<User> findAll();
//新增
void add(User user);
//删除
void delete(int id);
//修改
void update(User user);
}
<update id="update" parameterType="com.itbaizhan.pojo.User">
update user set username1 = #{username1},sex1 = #{sex1},address1 = #{address1}
</update>
@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<User> findAll();
//新增
void add(User user);
//删除
void delete(int id);
//修改
void update(User user);
//根据id查询
User findById(int id);
}
<select id="findById" parameterType="int" resultType="com.itbaizhan.pojo.User">
select * from user where id = #{id}
</select>
@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<User> 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);
}
}
MyBatis如何绑定实体类的参数
1、使用${}绑定参数
${}绑定参数相当于JDBC里边的Statement对象,是通过字符串拼接实现的绑定,所以${}绑定参数会出现sql注入的问题
<select id="findByUsernameLike" parameterType="string" resultType="com.itbaizhan.pojo.User">
select * from user where username like '%${value}%'
</select>
//测试方法写法如下:
@Test
public void testFindByNameLike(){
List<User> users = userMapper.findByUsernameLike("尚学堂");
users.forEach(System.out::println);
}
根据log4j输出的加载信息可以看到,条件是直接拼接到sql语句中的,所以这就会出现sql注入的问题
2、使用#{}绑定参数
#{}绑定参数相当于JDBC里的PreparedStatement对象,PreparedStatement对象是通过预编译,然后绑定参数的方式实现避免sql注入
<select id="findByUsernameLike" parameterType="string" resultType="com.itbaizhan.pojo.User">
select * from user where username1 like #{value}
</select>
@Test
public void testFindByNameLike(){
List<User> users = userMapper.findByUsernameLike("%尚学堂%");
users.forEach(System.out::println);
}
3、使用bind标签定义参数
如果使用#还不想在调用方法的参数中添加%,可以使用<bind>,<bind>允许我们在 Sql语句以外创建一个变量,并可以将其绑定到当前的Sql语句中。用法如下:
<select id="findByUsernameLike" parameterType="string" resultType="com.itbaizhan.pojo.User">
<bind name="likeName" value="'%'+username+'%'"/>
select * from user where username like #{likeName}
</select>
${}和#{}绑定参数的区别
- #表示sql模板的占位符,$表示将字符串拼接到sql模板中。
- #可以防止sql注入,一般能用#就不用$。
- ${}内部的参数名必须写value。
其实也就相当于PreparedStatement和Statement的区别
如果方法有多个参数如何绑定
1、顺序传参
Sql中的参数使用arg0,arg1...或param1,param2...表示参数的顺序。此方法可读性较低,在开发中不建议使用。
1、持久层接口方法
/**
* 分页查询
* @param startIndex 开始索引
* @param pageSize 每页条数
* @return
*/
List<User> findPage(int startIndex,int pageSize);
2、映射文件
<select id="findPage" resultType="com.itbaizhan.pojo.User">
select * from user limit #{arg0},#{arg1}
</select>
3、测试
@Test
public void testFindPage(){
List<User> users = userMapper.findPage(0,3);
users.forEach(System.out::println);
}
2、@Param传参
1、持久层接口方法
/**
* 分页查询
* @param startIndex 开始索引
* @param pageSize 每页条数
* @return
*/
List<User> findPage(int startIndex,int pageSize);
2、映射文件
<select id="findPage" resultType="com.itbaizhan.pojo.User">
select * from user limit #{param1},#{param2}
</select>
3、测试
@Test
public void testFindPage(){
List<User> users = userMapper.findPage(0,3);
users.forEach(System.out::println);
}
3、POJO传参
自定义POJO类,该类的属性就是要传递的参数,
在SQL语句中绑定参数时使用POJO的属性名作为参数名即可。此方式推荐使用。
1、持久层接口创建方法
List<User> 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、映射对象添加对应标签
<select id="findPage2" resultType="com.itbaizhan.pojo.User" parameterType="com.itbaizhan.pojo.PageQuery">
select * from user limit #{startIndex},#{pageSize}
</select>
4、测试
@Test
public void testFindPage2(){
PageQuery pageQuery=new PageQuery(3, 3);
List<User> users = userMapper.findPage2(pageQuery);
users.forEach(System.out::println);
}
4、Map传参
如果不想自定义POJO,可以使用Map作为传递参数的载体,在SQL语句中绑定参数时使用Map的Key作为参数名即可。此方法推荐使用。
1、持久层接口添加方法
List<User> findPage3(Map<String,Object> params);
2、映射文件中添加对应标签
<select id="findPage3" resultType="com.itbaizhan.pojo.User" parameterType="map">
select * from user limit #{startIndex},#{pageSize}
</select>
3、测试
@Test
public void testFindPage3(){
Map<String,Object> params = new HashMap();
params.put("startIndex",0);
params.put("pageSize",4);
List<User> users = userMapper.findPage3(params);
users.forEach(System.out::println);
}
ParameterType标签
可能现在还有童鞋不知道ParameterType标签是干什么用的,其实前面的映射文件注意事项里讲过,ParameterType标签就是用来绑定参数的,但是它只能绑定一个参数,也就是说如果方法有多个参数就只能通过上面的四种方式任意一种绑定
MyBatis核心配置文件SqlMapConfig中的标签
结构
-configuration
-properties(属性)
-property
-settings(全局配置参数)
-setting
-plugins(插件)
-plugin
-typeAliases(别名)
-typeAliase
-package
-environments(环境)
-environment
-transactionManager(事务管理)
-dataSource(数据源)
-mappers(映射器)
-mapper
-package
properties标签
属性值定义。properties标签中可以定义属性值,也可以引入外部配置文件。无论是内部定义还是外部引入,都可以使用${name}获取值。
例如:我们可以将数据源配置写到外部的db.properties中,再使用properties标签引入外部配置文件,这样可以做到动态配置数据源。
settings标签
plugin标签
<plugins>是配置MyBatis插件的。插件可以增强MyBatis功能,比如进行sql增强,打印日志,异常处理等。后期我们会使用该标签配置分页插件。
typeAliases标签
作用:该标签的作用就是用来给类起别名的
1、为一个类配置别名
为一个类配置别名
<typeAliases>
<typeAlias type="全类名" alias="别名"></typeAlias>
</typeAliases>
此时我们即可在映射文件中使用自定义别名,如:
1. 配置文件:
<typeAliases>
<typeAlias type="com.itbaizhan.pojo.User" alias="User"></typeAlias>
</typeAliases>
2. 映射文件:
<select id="findAll" resultType="User">
select * from user
</select>
2、为一个所有包下的所有类配置别名
<typeAliases>
<package name="包名"></package>
</typeAliases>
此时该包下的所有类都有了别名,别名代替了包名。如:
3. 配置文件:
<typeAliases>
<package name="com.itbaizhan.pojo"></package>
</typeAliases>
4. 映射文件:
<select id="findPage2" resultType="User" parameterType="PageQuery">
select * from user limit #{startIndex},#{pageSize}
</select>
environment标签
transactionManager标签
作用:控制是否做事务处理
<environments default="mysql">
<environment id="mysql">
<!-- JDBC:使用JDBC的提交和回滚 MANAGED:不做事务处理-->
<transactionManager type="JDBC"></transactionManager>
</environment>
</environments>
dataSource标签
连接池
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<!-- 连接池设置 -->
<dataSource type="POOLED">
<!-- 数据源设置... -->
</dataSource>
</environment>
</environments>
dataSource的type属性:
- POOLED:使用连接池管理连接,使用MyBatis自带的连接池。
- UNPOOLED:不使用连接池,直接由JDBC连接。
- JNDI:由JAVAEE服务器管理连接,如果使用Tomcat作为服务器则使用Tomcat自带的连接池管理。
mapper标签
<mappers>用于注册映射文件或持久层接口,只有注册的映射文件才能使用,共有四种方式都可以完成注册:
1、使用相对路径注册映射文件
使用mapper标签的resource属性实现使用相对路径注册映射文件
<mappers>
<mapper resource="com/itbaizhan/mapper/UserMapper.xml"/>
</mappers>
2、使用绝对路径注册映射文件
使用mapper标签的url属性实现使用绝对路径注册映射文件
<mappers>
<mapper url="file:///C:\Users\a\IdeaProjects\mybatiscase\mybatisDemo1\src\main\resources\com\itbaizhan\mapper\UserMapper.xml"/>
</mappers>
3、注册持久层接口
使用mapper标签的class属性实现注册持久层接口
<mappers>
<mapper class="com.itbaizhan.mapper.UserMapper"/>
</mappers>
4、注册一个包下的所有持久层接口
使用package标签的name属性实现注册一个包下的所有持久层接口
<mappers>
<package name="com.itbaizhan.mapper"/>
</mappers>
MyBatis映射文件标签
当POJO属性名和数据库列名不一致时
Result标签的作用:自定义映射关系。
MyBatis可以将数据库结果集封装到对象中,是因为结果集的列名和对象属性名相同:
当POJO属性名和数据库列名不一致时,MyBatis无法自动完成映射关系。如:
此时有两种解决方案:
Sql语句的查询字段起与POJO属性相同的别名
<select id="findAll" resultType="com.itbaizhan.pojo.Teacher">
select tid as id,tname as teacherName from teacher;
</select>
自定义映射文件Result
• 在映射文件中,使用<resultMap>自定义映射关系:
<!-- id:自定义映射名 type:自定义映射的对象类型 -->
<resultMap id="teacherMapper" type="com.itbaizhan.pojo.Teacher">
<!-- id定义主键列 property:POJO属性名 column:数据库列名 -->
<id property="id" column="tid"></id>
<!-- result定义普通列 property:POJO属性名 column:数据库列名 -->
<result property="teacherName" column="tname"></result>
</resultMap>
• 在<select>标签中,使用resultMap属性代替resultType属性,使用自定义映射关系。
<select id="findAll" resultMap="teacherMapper">
select * from teacher
</select>
特殊字符处理
在Mybatis映射文件中尽量不要使用一些特殊字符,如:<,>等。
我们可以使用符号的实体来表示:
符号 | 实体 |
< | < |
> | > |
& | & |
‘ | ' |
“ | " |
动态sql
一个查询的方法的Sql语句不一定是固定的。比如电商网站的查询商品,用户使用不同条件查询,Sql语句就会添加不同的查询条件。此时就需要在方法中使用动态Sql语句。
if标签
<if>标签内的Sql片段在满足条件后才会添加,
用法为:<if test="条件">。例如:根据不同条件查询用户:
1、持久层接口创建方法
// 用户通用查询
List<User> findByCondition(User user);
2、映射文件添加对应标签
<select id="findByCondition" resultType="com.itbaizhan.pojo.User" parameterType="com.itbaizhan.pojo.User">
select * from user where 1=1
<if test="username1 != null and username1.length() != 0">
and username1 like #{username1}
</if>
<if test="sex1 != null and sex1.length() != 0">
and sex1 = #{sex1}
</if>
<if test="address1 != null and address1.length() != 0">
and address1 = #{address1}
</if>
</select>
3、测试
@Test
public void testFindByCondition(){
User user = new User("%尚学堂%","男");
List<User> users = userMapper.findByCondition(user);
users.forEach(System.out::println);
}
where标签
<where>可以自动添加where,并代替sql中的where 1=1 和第一个and,
更符合程序员的开发习惯,使用<where>后的映射文件如下:
我们通过上面if标签的findByCondition方法修改一下映射文件中对应的标签进行测试
<select id="findByCondition" resultType="com.itbaizhan.pojo.User" parameterType="com.itbaizhan.pojo.User">
select * from user
<where>
<if test="username1 != null and username1.length() != 0">
username1 like #{username1}
</if>
<if test="sex1 != null and sex1.length() != 0">
and sex1 = #{sex1}
</if>
</where>
</select>
set标签
<set>标签用在update语句中。借助<if>,可以只对有具体值的字段进行更新。<set>会自动添加set关键字,并去掉最后一个if语句中多余的逗号。
1、持久层创建方法
//修改
void update(User user);
2、映射文件对应标签
<update id="update" parameterType="com.itbaizhan.pojo.User">
update user
<set>
<if test="username1 != null and username1.length() > 0">
username1 = #{username1},
</if>
<if test="sex1 != null and sex1.length() > 0">
sex1 = #{sex1},
</if>
</set>
<where>
id = #{id}
</where>
</update>
3、测试
@Test
public void testUpdate() throws IOException {
//调用方法
User user = new User(16,"浩克");
userMapper.update(user);
//提交事务
session.commit();
}
choose标签和when标签和otherwise标签
这些标签表示多条件分支,类似JAVA中的switch...case。<choose>类似switch,<when>类似case,<otherwise>类似default,用法如下:
1、持久层接口创建方法
// 用户通用查询2
List<User> findByCondition2(User user);
2、映射文件创建对应标签
<select id="findByCondition2" resultType="com.itbaizhan.pojo.User" parameterType="com.itbaizhan.pojo.User">
<bind name="likeName" value="'%'+username1+'%'"/>
select * from user
<where>
<choose>
<when test="username1.length() < 5">
username1 like #{likeName}
</when>
<when test="username1.length() < 10">
username1 = #{username1}
</when>
<otherwise>
id = 1
</otherwise>
</choose>
</where>
</select>
1、用户名<5模糊查询
@Test
public void testFindByCondition2(){
User user = new User("尚学堂");
List<User> users = userMapper.findByCondition2(user);
users.forEach(System.out::println);
}
2、5<=用户名<10 精准查询
@Test
public void testFindByCondition2(){
User user = new User("尚学堂666");
List<User> users = userMapper.findByCondition2(user);
users.forEach(System.out::println);
}
3、用户名>=10 直接查询id=1的数据
@Test
public void testFindByCondition2(){
User user = new User("尚学堂66666666");
List<User> users = userMapper.findByCondition2(user);
users.forEach(System.out::println);
}
foreach标签
<foreach>类似JAVA中的for循环,可以遍历集合或数组。<foreach>有如下属性:
- collection:遍历的对象类型
- open:开始的sql语句
- close:结束的sql语句
- separator:遍历每项间的分隔符
- item:表示本次遍历获取的元素,遍历List、Set、数组时表示每项元素,遍历map时表示键值对的值。
- index:遍历List、数组时表示遍历的索引,遍历map时表示键值对的键。
foreach标签遍历数组
我们使用<foreach>遍历数组进行批量删除。
1、持久层接口创建方法
//批量删除
void deleteBatch(int[] ids);
2、映射文件添加对应标签
<delete id="deleteBatch" parameterType="int">
delete from user
<where>
<foreach collection="array" open="id in(" close=")" separator="," item="id">
#{id}
</foreach>
</where>
</delete>
3、测试
@Test
public void testDeleteBatch(){
int[] ids = new int[]{16,19,20};
userMapper.deleteBatch(ids);
//提交事务
session.commit();
}
删除前
删除后
foreach标签遍历Collection
1、持久层添加方法
//批量添加
void insertBatch(List<User> users);
2、映射文件添加对应标签
<insert id="insertBatch" parameterType="com.itbaizhan.pojo.User">
insert into user
<foreach collection="list" open="values" separator="," item="user">
(#{user.id},#{user.username1},#{user.sex1},#{user.address1})
</foreach>
</insert>
3、测试
@Test
public void testInsertBatch(){
User user = new User("一拳超人","男","网络");
User user1 = new User("路飞","男","海贼王");
List<User> list = new ArrayList<>();
list.add(user);
list.add(user1);
//添加一批数据
userMapper.insertBatch(list);
//事务提交
session.commit();
}
添加数据前
添加数据后
foreach标签遍历Map
我们使用<foreach>遍历Map进行多条件查询。
1、持久层添加方法
/**
* 多条件查询
* @param map 查询的条件键值对 键:属性名 值:属性值
* @return
*/
List<User> findUser(@Param("queryMap") Map<String,Object>map);
2、映射文件添加对应标签
<select id="findUser" parameterType="map" resultType="com.itbaizhan.pojo.User">
select * from user
<where>
<foreach collection="queryMap" separator="and" index="key" item="value">
${key} = #{value}
</foreach>
</where>
</select>
3、测试
@Test
public void testFindUser(){
Map<String,Object> queryMap = new HashMap();
queryMap.put("sex1","男");
queryMap.put("address1","北京");
List<User> users = userMapper.findUser(queryMap);
users.forEach(System.out::println);
}
缓存
缓存是内存当中一块存储数据的区域,目的是提高查询效率。MyBatis会将查询结果存储在缓存当中,当下次执行相同的SQL时不访问数据库,而是直接从缓存中获取结果,从而减少服务器的压力。
- 什么是缓存?
存在于内存中的一块数据。 - 缓存有什么作用?
减少程序和数据库的交互,提高查询效率,降低服务器和数据库的压力。 - 什么样的数据使用缓存?
经常查询但不常改变的,改变后对结果影响不大的数据。 - MyBatis缓存分为哪几类?
一级缓存和二级缓存 - 如何判断两次Sql是相同的?
- 查询的Sql语句相同
- 传递的参数值相同
- 对结果集的要求相同
- 预编译的模板Id相同
一级缓存
- MyBatis一级缓存也叫本地缓存。SqlSession对象中包含一个Executor对象,Executor对象中包含一个PerpetualCache对象,在该对象存放一级缓存数据。
- 由于一级缓存是在SqlSession对象中,所以只有使用同一个SqlSession对象操作数据库时才能共享一级缓存。
- MyBatis的一级缓存是默认开启的,不需要任何的配置。
清空一级缓存
进行以下操作可以清空MyBatis一级缓存:
- SqlSession调用close():操作后SqlSession对象不可用,该对象的缓存数据也不可用。
- SqlSession调用clearCache()/commit():操作会清空一级缓存数据。
- SqlSession调用增删改方法:操作会清空一级缓存数据,因为增删改后数据库发生改变,缓存数据将不准确。
二级缓存
- MyBatis二级缓存也叫全局缓存。数据存放在SqlSessionFactory中,只要是同一个工厂对象创建的SqlSession,在进行查询时都能共享数据。一般在项目中只有一个SqlSessionFactory对象,所以二级缓存的数据是全项目共享的。
- MyBatis一级缓存存放的是对象,二级缓存存放的是对象的数据。所以要求二级缓存存放的POJO必须是是要实现Serializable接口。
- MyBatis二级缓存默认不开启,手动开启后数据先存放在一级缓存中,
- 只有一级缓存数据失效后,数据才会存到二级缓存中。
SqlSession调用clearCache()无法将数据存到二级缓存中。
开启二级缓存
开启二级缓存需要两步:
①MyBatis配置文件添加如下设置:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
②在映射文件添加<cache />标签,该映射文件下的所有方法都支持二级缓存。
如果查询到的集合中对象过多,二级缓存只能缓存1024个对象引用。可以通过<cache />标签的size属性修改该数量。
<cache size="2048"/>
测试二级缓存
1、持久层创建方法
//根据id查询
User findById(int id);
2、映射文件添加对应标签
<select id="findById" parameterType="int" resultType="com.itbaizhan.pojo.User">
select * from user where id = #{id}
</select>
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的关联查询分为一对一关联查询和一对多关联查询。
- 查询对象时,将关联的另一个对象查询出来,就是一对一关联查询。
- 查询对象时,将关联的另一个对象的集合查询出来,就是一对多关联查询。
association标签
mybatis进行多表查询时会用上association标签,它的属性包括property,column,javaType等,它的作用是让实体类对象与数据库表的列相互对应,以便让mybatis可以进行多表查询。
使用的场景:association的使用场景为1:1的情况。
<!--一对一对象列 property:属性名 column:关联列名 javaType:对象类型-->
<association property="classes" column="classid" javaType="com.itbaizhan.pojo.Classes">
<!--关联主键列-->
<id property="cid" column="cid"></id>
<!--关联普通列-->
<result property="className" column="className"></result>
</association>
collection标签
作用:在Mybaits中collection标签是用来实现连表查询的。
使用的场景:collection的使用场景为1:n和n:n两种情况。
添加的内容:使用collection的时候需要在类中添加关联集合(查询哪个类就在哪个类中添加)。
<!-- 集合列 property:属性名 column:关联列名 ofType:集合的泛型 -->
<collection property="studentList" column="classId" ofType="com.itbaizhan.pojo.Student">
<id property="sid" column="sid"></id>
<result property="name" column="name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
</collection>
所以在关联查询之前我们需要有几张有关联的表,并且创建对应的POJO类
import java.util.List;
public class Classes {
private Integer cid;
private String className;
private List<Student> studentList;
public Classes(Integer cid, String className, List<Student> 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<Student> getStudentList() {
return studentList;
}
public void setStudentList(List<Student> 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<Student> findAll();
}
2、映射文件添加对应标签
<resultMap id="studentMapper" type="com.itbaizhan.pojo.Student">
<!-- 主键列 -->
<id property="sid" column="sid"></id>
<!-- 普通列 -->
<result property="name" column="name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<!-- 一对一对象列 -->
<association property="classes" column="classId" javaType="com.itbaizhan.pojo.Classes">
<!-- 主键列 -->
<id property="cid" column="cid"></id>
<!-- 普通列 -->
<result property="className" column="className"></result>
</association>
</resultMap>
<select id="findAll" resultMap="studentMapper">
select * from student left join classes on student.classId = classes.cid
</select>
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<Student> list = studentMapper.findAll();
list.forEach(System.out::println);
}
}
一对多关联查询
学生对于教室来说就是一对多的关系,一个教室可以有多个学生,所以我们查询教室顺便把这个教室的所有学生查询出来
1、持久层接口创建方法
//查询所有教室
List<Classes> findAll();
2、映射文件添加对应标签
<?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.itbaizhan.mapper.ClassesMapper">
<resultMap id="classesMapper" type="com.itbaizhan.pojo.Classes">
<!-- 主键列 -->
<id property="cid" column="cid"></id>
<!-- 普通列 -->
<result property="className" column="className"></result>
<!-- 一对多对象列 -->
<collection property="studentList" column="cid" ofType="com.itbaizhan.pojo.Student">
<!-- 学生表主键列 -->
<id property="sid" column="sid"></id>
<!-- 学生表普通列 -->
<result property="name" column="name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
</collection>
</resultMap>
<select id="findAll" resultMap="classesMapper">
select * from classes left join student on classes.cid = student.classId
</select>
</mapper>
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<Classes> list = classesMapper.findAll();
list.forEach(System.out::println);
}
}
多对多关联查询
MyBatis多对多关联查询本质就是两个一对多关联查询。
例如有老师类和班级类:
一个老师对应多个班级,也就是老师类中有一个班级集合属性。
一个班级对应多个老师,也就是班级类中有一个老师集合属性。
所以我们需要有三张表,老师表、教室表和中间表,并且创建对应的POJO类
public class Teacher {
private Integer tid;
private String tname;
private List<Classes> classesList;
public Teacher(Integer tid, String tname, List<Classes> 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<Classes> getClassesList() {
return classesList;
}
public void setClassesList(List<Classes> 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<Student> studentList;
private List<Teacher> teacherList;
public Classes(Integer cid, String className, List<Student> studentList, List<Teacher> 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<Student> getStudentList() {
return studentList;
}
public void setStudentList(List<Student> studentList) {
this.studentList = studentList;
}
public List<Teacher> getTeacherList() {
return teacherList;
}
public void setTeacherList(List<Teacher> teacherList) {
this.teacherList = teacherList;
}
@Override
public String toString() {
return "Classes{" +
"cid=" + cid +
", className='" + className + '\'' +
", studentList=" + studentList +
", teacherList=" + teacherList +
'}';
}
}
1、持久层创建方法
public interface TeacherMapper {
//查询所有老师
List<Teacher> findAll();
}
2、映射文件添加对应标签
<?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.itbaizhan.mapper.TeacherMapper">
<resultMap id="teacherMapper" type="com.itbaizhan.pojo.Teacher">
<!-- 主键列 -->
<id property="tid" column="tid"></id>
<!-- 普通列 -->
<result property="tname" column="tname"></result>
<!-- 一对多对象列 -->
<collection property="classesList" column="tid" ofType="com.itbaizhan.pojo.Classes">
<!-- 主键列 -->
<id property="cid" column="cid"></id>
<!-- 普通列 -->
<result property="className" column="className"></result>
</collection>
</resultMap>
<select id="findAll" resultMap="teacherMapper">
select * from teacher
left join classes_teacher
on teacher.tid = classes_teacher.tid
left join classes
on classes_teacher.cid = classes.cid
</select>
</mapper>
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<Teacher> 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<Classes> findAll();
}
public interface StudentMapper {
//查找所有学生
List<Student> findAll();
//根据classId查找学生
Student findByCid(int classId);
}
2、映射文件添加对应标签
ClassesMapper.xml
<!-- 分解式查询-->
<resultMap id="classesMapper" type="com.itbaizhan.pojo.Classes">
<!-- 主键列 -->
<id property="cid" column="cid"></id>
<!-- 普通列 -->
<result property="className" column="className"></result>
<!-- select:从表查询调用的方法 column:调用方法时传入的参数字段 -->
<collection
property="studentList"
column="cid"
ofType="com.itbaizhan.pojo.Student"
select="com.itbaizhan.mapper.StudentMapper.findByCid">
</collection>
</resultMap>
<select id="findAll" resultMap="classesMapper">
select * from classes
</select>
StudentMapper.xml
<select id="findByCid" resultType="com.itbaizhan.pojo.Student" parameterType="int">
select * from student where classId = #{cid}
</select>
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<Classes> list = classesMapper.findAll();
list.forEach(System.out::println);
}
}
select属性和column属性
select:从表查询调用的方法
column:调用方法时传入的参数字段
延迟加载
分解式查询又分为两种加载方式:
- 立即加载:在查询主表时就执行所有的Sql语句。
- 延迟加载:又叫懒加载,首先执行主表的查询语句,使用从表数据时才触发从表的查询语句。
延迟加载在获取关联数据时速度较慢,但可以节约资源,即用即取。
开启延迟加载
- 设置所有的N+1查询都为延迟加载:
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
- 设置某个方法为延迟加载:
在<association>、<collection>中添加fetchType属性设置加载方式。lazy:延迟加载;eager:立即加载。
一般情况下,一对多查询使用延迟加载,一对一查询使用立即加载。
注解开发
环境搭配
MyBatis可以使用注解替代映射文件。映射文件的作用就是定义Sql语句,可以在持久层接口上使用@Select/@Delete/@Insert/@Update定义Sql语句,这样就不需要使用映射文件了。
- 创建maven工程,引入依赖
- 创建mybatis核心配置文件SqlMapConfig.xml
- 将log4j.properties文件放入resources中,让控制台打印SQL语句。
- 创建实体类
- 创建持久层接口,并在接口方法上定义Sql语句
public interface UserMapper {
@Select("select * from user")
List<User> 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<User> list = userMapper.findAll();
list.forEach(System.out::println);
}
}
增删改查注解
public interface UserMapper {
@Select("select * from user")
List<User> 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注解回填
@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插入数据后,主键回填了
动态sql
1、使用脚本标签
将Sql嵌套在<script>内即可使用动态Sql标签
2、在方法中构建动态Sql
在MyBatis中有@SelectProvider、@UpdateProvider、@DeleteProvider、@InsertProvider注解。当使用这些注解时将不在注解中直接编写SQL,而是调用某个类的方法来生成SQL。
自定义映射@Result
当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<User> findAll();
@Test
public void testFindAll() throws IOException {
//调用方法
List<User> list = userMapper.findAll();
list.forEach(System.out::println);
}
开启二级缓存@CacheNamespace
MyBatis默认开启一级缓存,接下来我们学习如何在注解开发时使用二级缓存:
- POJO类实现Serializable接口。
- 在MyBatis配置文件添加如下设置:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings> - 在持久层接口上方加注解@CacheNamespace(blocking=true),该接口的所有方法都支持二级缓存。
一对一关联查询
在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<Student> 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<Student> 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<Classes> 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<Classes> list = classesMapper.findAll();
list.forEach(System.out::println);
}
}
注解和映射文件对比
MyBatis中更推荐使用映射文件开发,Spring、SpringBoot更推荐注解方式。具体使用要视项目情况而定。它们的优点对比如下:
映射文件:
- 代码与Sql语句是解耦的,修改时只需修改配置文件,无需修改源码。
- Sql语句集中,利于快速了解和维护项目。
- 级联查询支持连接查询和分解查询两种方式,注解开发只支持分解查询。
注解:
- 配置简单,开发效率高。
- 类型安全,在编译期即可进行校验,不用等到运行时才发现错误。
分页插件PageHelper
PageHelper是一款非常好用的开源免费的Mybatis第三方分页插件。使用该插件时,只要传入分页参数,即可自动生成页面对象。我们使用该插件分页查询所有用户:
1. 引入依赖
<!-- PageHelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.0</version>
</dependency>
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 设置数据库类型-->
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
@Test
public void testFindPage() {
// (1)查询前设置分页参数,参数一:页数,从1开始。参数二:每页条数
PageHelper.startPage(1, 3);
// (2)正常查询
List<User> 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());
}
MyBatisGenerator工具
MyBatis Generator(MBG)是MyBatis官方提供的代码生成器。它可以根据数据库的表结构自动生成POJO类、持久层接口与映射文件,极大减少了代码的编写量,提高开发效率。
MBG可以作为项目引入使用,也可以作为Maven插件使用,其中作为Maven插件使用更加方便快捷。
在pom文件中配置MBG插件
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.7</version>
<configuration>
<!-- MBG配置文件位置 -->
<configurationFile>src/main/resources/generatorConfig.xml</configurationFile>
<!-- 运行显示详情 -->
<verbose>true</verbose>
<!-- 允许覆盖文件 -->
<overwrite>true</overwrite>
</configuration>
</plugin>
</plugins>
</build>
编写generatorConfig配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!-- jdbc的jar包位置,插件需要连接数据库 -->
<classPathEntry location="F:\repository\mysql\mysql-connector-java\8.0.26\mysql-connector-java-8.0.26.jar"/>
<context id="default" targetRuntime="MyBatis3">
<!-- 是否去除自动生成的注释-->
<commentGenerator>
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!--数据库连接参数-->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis"
userId="root"
password="root"></jdbcConnection>
<!-- 类型处理器,在数据库类型和java类型之间的转换控制-->
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!-- targetProject:JAVA类路径 targetProject:生成的POJO类的包-->
<javaModelGenerator targetProject="src/main/java" targetPackage="com.itbaizhan.pojo">
<!-- 是否生成子包 -->
<property name="enableSubPackages" value="false"/>
<!-- 设置是否在getter方法中,对String类型字段调用trim()方法 -->
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!-- targetProject:配置文件路径 targetPackage:生成映射文件的位置 -->
<sqlMapGenerator targetProject="src/main/resources" targetPackage="com.itbaizhan.mapper">
<!-- 是否生成子包 -->
<property name="enableSubPackages" value="false"/>
</sqlMapGenerator>
<!-- targetPackage:JAVA类路径 targetProject:生成的持久层接口包 -->
<javaClientGenerator targetProject="src/main/java" targetPackage="com.itbaizhan.mapper" type="XMLMAPPER">
<!-- 是否生成子包 -->
<property name="enableSubPackages" value="false"/>
</javaClientGenerator>
<!-- 数据库表,表名不要和其他库中的表名一样 -->
<table tableName="product"></table>
</context>
</generatorConfiguration>
运行插件
- 运行插件,自动生成POJO,持久层接口,映射文件:
- Product.java:POJO类
- ProductMapper.java:持久层接口
- ProductMapper.xml:映射文件
- ProductExample.java:查询扩展类,该类可以构造复杂的查询条件。
- Criterion:代表一个字段。
- GeneratedCriteria:抽象类,生成查询条件的工具。
- Criteria:GeneratedCriteria的子类,生成查询条件的工具。
有些同学可能不知道为啥突然就添加了持久层接口,这是因为我们在配置generatorConfig.xml核心配置文件的时候,添加了table标签,该标签就是用来告诉generator应该生成数据库中哪个表的POJO类和映射文件
<!-- 数据库表,表名不要和其他库中的表名一样 -->
<table tableName="product"></table>
增删改方法
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对象包含在一起