目录
一、MyBatis介绍
1.1 什么是框架
框架即一个半成品软件。开发者从头开发一个软件需要花费大量精力,于是有一些项目组开发出半成品软件,开发者在这些软件的基础上进行开发,这样的软件就称之为框架。
如果将开发完成的软件比作是一套已经装修完毕的新房,框架就好比是一套已经修建好的毛坯房。用户直接购买毛坯房,保证建筑质量和户型合理的同时可以进行风格的自由装修。
使用框架开发的好处:
- 省去大量的代码编写、减少开发时间、降低开发难度。
- 限制程序员必须使用框架规范开发,增强代码的规范性,降低程序员之间沟通及日后维护的成本。
- 将程序员的注意力从技术中抽离出来,更集中在业务层面。
使用框架就好比和世界上最优秀的软件工程师共同完成一个项目,并且他们完成的还是基础、全局的工作。
1.2什么是ORM框架
ORM(Object Relationl Mapping),对象关系映射,即在数据库和对象之间作映射处理。
1.3什么是MyBatis
MyBatis是一个半自动的ORM框架,其本质是对JDBC的封装。使用MyBatis不需要写JDBC代码,但需要程序员编写SQL语句。之前是apache的一个开源项目iBatis,2010年改名为MyBatis。
补充:
Hibernate也是一款持久层ORM框架,多年前的市场占有率很高,但近年来市场占有率越来越低。
MyBatis与Hibernate的比较:
- MyBatis是一个半自动的ORM框架,需要手写SQL语句。
- Hibernate是一个全自动的ORM框架,不需要手写SQL语句。
- 使用MyBatis的开发量要大于Hibernate。
为什么Hibernate市场占有率越来越低:
- 对于新手学习Hibernate时间成本比MyBatis大很多,MyBatis上手很快。
- Hibernate不需要写SQL语句是因为框架来生成SQL语句。对于复杂查询,开发者很难控制生成的SQL语句,这就导致SQL调优很难进行。
- 之前的项目功能简单,数据量小,所以使用Hibernate可以快速完成开发。而近年来项目的数据量越来越大,而互联网项目对查询速度要求也很高,这就要求我们一定要精细化的调整SQL语句。此时灵活性更强,手动编写SQL语句的MyBatis慢慢代替了Hibernate使用。
- 在高并发、大数据、高性能、高响应的互联网项目中,MyBatis是首选的持久框架。而对于对性能要求不高的比如内部管理系统等可以使用Hibernate。
二、入门案例
2.1 环境搭建
1、创建maven工程,引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zj</groupId>
<artifactId>learnMyBatis</artifactId>
<version>1.0-SNAPSHOT</version>
<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>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!--log4j日志-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
</dependencies>
</project>
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.cj.jdbc.Driver"/>
<property name="url" value="jdbc:///mybatis"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
</configuration>
3、将log4j.properties文件放入resources中,让控制台打印SQL语句。
# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE debug info warn error fatal
log4j.rootCategory=debug, CONSOLE
# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
#log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=[%d{MM/dd HH:mm:ss}] %-6r [%15.15t] %-5p %30.30c %x - %m\n
4、创建实体类
package com.zj;
public class User {
private int id;
private String username;
private String sex;
private String address;
//构造、get\set略
}
2.2 创建持久层接口和映射文件
1、在java目录创建持久层接口
package com.zj.mapper;
import com.zj.pojo.User;
import java.util.List;
/*主要操作User类和数据库之间的交互*/
public interface UserMapper {
List<User> selectAllUser();
}
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">
<!--namespace;指定当前的映射文件对应哪个接口-->
<mapper namespace="com.zj.mapper.UserMapper">
<!--id="selectAllUser";id属性值为接口的方法名称
resultType="com.zj.pojo.User";定义返回值类型
-->
<select id="selectAllUser" resultType="com.zj.pojo.User">
SELECT * FROM user;
</select>
</mapper>
3、将映射文件配置到mybatis核心配置文件中
<!--注册映射文件(项目加载的时候先加载的是核心配置文件)-->
<mappers>
<mapper resource="com.zj.mapper.UserMapper.xml"/>
</mappers>
映射文件注意事项:
映射文件要和接口名称相同。
映射文件要和接口的目录结构相同,而且映射文件所在的文件结构要一层层的建。
映射文件中namespace属性要写接口的全名。
映射文件中标签的id属性是接口方法的方法名。
映射文件中标签的resultType属性是接口方法的返回值类型。
映射文件中标签的parameterType属性是接口方法的参数类型。
映射文件中resultType、parameterType属性要写全类名,如果是集合类型,则写其泛型的全类名。
2.3 测试持久层接口方法
import com.zj.mapper.UserMapper;
import com.zj.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class TestUserMapper {
@Test
public void TestSelectAllUser() throws IOException {
/*1、读取核心配置文件*/
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
/*2、SqlSessionFactoryBuilder对象获取SqlSessionFactory对象(建造者用映射文件材料建设了工厂)*/
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sessionFactory = sqlSessionFactoryBuilder.build(resourceAsStream);
/*3、SqlSessionFactory对象获取SqlSession(工厂建造了了SqlSession)*/
SqlSession sqlSession = sessionFactory.openSession();
/*4、SqlSession 对象获取接口的代理对象*/
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
/*5、代理对象执行方法*/
List<User> users = mapper.selectAllUser();
for (User user : users) {
System.out.println(user);
}
/*6、释放资源*/
sqlSession.close();
resourceAsStream.close();
}
}
2.4 MyBatis核心对象及工作流程
MyBatis核心对象
-
SqlSessionFactoryBuilder
SqlSession工厂构建者对象,使用构造者模式创建SqlSession工厂对象。
-
SqlSessionFactory
SqlSession工厂,使用工厂模式创建SqlSession对象。
-
SqlSession
该对象可以操作数据库,也可以使用动态代理模式创建持久层接口的代理对象操作数据库。
-
Mapper
持久层接口的代理对象,他具体实现了持久层接口,用来操作数据库。
MyBatis工作流程
-
创建SqlSessionFactoryBuilder对象
-
SqlSessionFactoryBuilder对象构建了SqlSessionFactory对象:构造者模式
-
SqlSessionFactory对象生产了SqlSession对象:工厂模式
-
SqlSession对象创建了持久层接口的代理对象:动态代理模式
-
代理对象操作数据库
2.5 使用SqlSession操作数据库(了解)
除了代理对象能够操作数据库,SqlSession也能操作数据库。只是这种方式在开发中使用的较少,接下来我们使用SqlSession操作数据库:
@Test
public void testFindAll2() throws Exception {
// (1)读取核心配置文件
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
// (2)创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
// (3)SqlSessionFactoryBuilder对象获取SqlSessionFactory对象
SqlSessionFactory factory = builder.build(is);
// (4)SqlSessionFactory对象获取SqlSession对象
SqlSession session = factory.openSession();
// (5)SqlSession直接操作数据库
List<User> users = session.selectList("com.zj.mapper.UserMapper.selectAllUser");
users.forEach(System.out::println);
// (6)关闭资源
session.close();
is.close();
}
2.6Mapper动态代理原理
通过源码了解MyBatis动态代理的实现。
点开测试类的getMapper
方法,发现SqlSession实际上是抽象类,找到该抽象类的默认的实现类DefaultSqlSession
查看代理方式
点开MapperProxy类,查看invoke方法,查看代理对象是如何工作的。
结论:
- SqlSession的getMapper方法,最终是调用的是JDK动态代理方法,生成一个代理对象,类型就是传入的接口类型。
- MapperProxy对象通过调用MapperMethod的execute方法定义了代理方式,该方法的底层调用的是SqlSession的方法。
三、MyBatis增删改查
3.1 新增
1、持久层接口添加方法
void addUser(User user);
2、映射文件添加标签
<insert id="addUser" parameterType="com.zj.pojo.User">
INSERT INTO user VALUES (default ,#{username},#{sex},#{address})
</insert>
3、编写测试方法
@Test
public void TestAddUser() throws IOException {
/*建造者*/
SqlSessionFactoryBuilder SQLSessionFactoryBuilder = new SqlSessionFactoryBuilder();
/*建造者根据配置文件建造工厂*/
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = SQLSessionFactoryBuilder.build(resourceAsStream);
/*工厂生产SqlSession*/
SqlSession sqlSession = factory.openSession();
/*SqlSession获取接口的代理对象*/
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
/*代理对象调用方法*/
mapper.addUser(new User(0,"ddd","男","上海"));
/*提交事务*/
sqlSession.commit();
/*释放资源*/
sqlSession.close();
resourceAsStream.close();
}
注意:
- 当接口方法的参数类型为POJO类型时,SQL语句中绑定参数时使用
#{POJO的属性名}
即可。- MyBatis事务默认手动提交,所以在执行完增删改方法后,需要手动调用SqlSession对象的事务提交方法,否则数据库将不发生改变。
3.2 修改
优化测试类
import com.zj.mapper.UserMapper;
import com.zj.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class TestUserMapper {
InputStream resourceAsStream = null;
SqlSession sqlSession = null;
UserMapper mapper =null;
/*优化*/
@Before
public void getSqlSession() throws IOException {
/*配置文件*/
resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
/*建造者*/
SqlSessionFactoryBuilder SQLSessionFactoryBuilder = new SqlSessionFactoryBuilder();
/*建造者根据配置文件创建工厂*/
SqlSessionFactory factory = SQLSessionFactoryBuilder.build(resourceAsStream);
/*工厂生产SqlSession*/
sqlSession = factory.openSession();
/*SqlSession获取代理对象*/
mapper = sqlSession.getMapper(UserMapper.class);
}
@Test
public void TestSelectAllUser() throws IOException {
List<User> users = mapper.selectAllUser();
for (User user : users) {
System.out.println(user);
}
}
@Test
public void TestAddUser() throws IOException {
/*代理对象调用方法*/
mapper.addUser(new User(0,"ddd","男","上海"));
/*提交事务*/
sqlSession.commit();
}
@After
public void close() throws IOException {
sqlSession.close();
resourceAsStream.close();
}
}
1、持久层接口添加方法
void updateUser(User user);
2、映射文件添加标签
<update id="updateUser" parameterType="com.zj.pojo.User">
UPDATE user SET username = #{username}, sex = #{sex},address = #{address} WHERE id = #{id}
</update>
3、编写测试方法
@Test
public void TestUpdateUser(){
mapper.updateUser(new User(7,"哈哈哈","女","青岛市"));
/*提交事务*/
sqlSession.commit();
}
3.3 根据id删除
1、持久层接口添加方法
/*根据id删除*/
void removeUser(int id);
2、映射文件添加标签
<delete id="removeUser" parameterType="int">
DELETE FROM user WHERE id = #{id}
</delete>
注:当方法的参数类型是简单数据类型时,#{}中可以写任意名称
- 简单数据类型:基本数据类型、字符串等
3、编写测试方法
@Test
public void TestRemoveUser(){
mapper.removeUser(7);
/*提交事务*/
sqlSession.commit();
}
3.4 模糊查询
1、持久层接口添加方法
/*模糊查询*/
List<User> getUserLikeName(String userName);
2、映射文件添加标签
<select id="getUserLikeName" parameterType="string" resultType="com.zj.pojo.User">
SELECT * FROM user WHERE username like #{username}
</select>
模糊查询如果不想在调用方法时参数加%,可以使用拼接参数的方式设置Sql:
<select id="getUserLikeName" parameterType="string" resultType="com.zj.pojo.User">
SELECT * FROM user WHERE username like '%${value}%'
</select>
如果使用#
还不想在调用方法的参数中添加%
,可以使用<bind>
,<bind>
允许我们在 Sql语句以外创建一个变量,并可以将其绑定到当前的Sql语句中。既能防止sql注入还能在传递参数的时候不需要传递百分号。用法如下:
<select id="getUserLikeName" parameterType="string" resultType="com.zj.pojo.User">
<bind name="likeName" value="'%'+username+'%'"/>
SELECT * FROM user WHERE username like #{likeName}
</select>
#和$的区别:
- #表示sql模板的占位符,$表示将字符串拼接到sql模板中。
- #可以防止sql注入,一般能用#就不用$。
- ${}内部的参数名必须写value。
我们看到在映射文件中,parameterType的值为
string
而没有写java.lang.String
,这是为什么呢?
- 参数/返回值类型为基本数据类型/包装类/String等类型时,我们可以写全类名,也可以写别名。
3、编写测试方法
/*模糊查询*/
@Test
public void TestGetUserLikeName(){
List<User> users = mapper.getUserLikeName("%三%");
for (User user : users) {
System.out.println(user);
}
如果使用sql拼接、或者是<bind>的话直接写参数就行了:
/*模糊查询*/
@Test
public void TestGetUserLikeName(){
List<User> users = mapper.getUserLikeName("三");
for (User user : users) {
System.out.println(user);
}
}
3.5 分页查询
分页查询时,Sql语句使用limit关键字,需要传入开始索引和每页条数两个参数。MyBatis的多参数处理有以下方式:
顺序传参
Sql中的参数使用arg0,arg1...或param1,param2...表示参数的顺序。此方法可读性较低,在开发中不建议使用。
@Param传参
在接口方法的参数列表中通过@Param定义参数名称,在Sql语句中通过注解中所定义的参数名称指定参数位置。此方式参数比较直观的,推荐使用。
1、持久层接口方法
/*分页查询*/
List<User> getUserByPage(@Param("start") int start, @Param("size") int size);
2、映射文件
<!--分页查询-->
<select id="getUserByPage" resultType="com.zj.pojo.User">
SELECT * FROM user LIMIT #{start},#{size}
</select>
3、测试类
/*分页查询*/
@Test
public void TestGetUserByPage(){
List<User> users = mapper.getUserByPage(0, 3);
for (User user : users) {
System.out.println(user);
}
}
POJO传参
自定义POJO类,该类的属性就是要传递的参数,在SQL语句中绑定参数时使用POJO的属性名作为参数名即可。此方式推荐使用。
1、自定义POJO
public class PageQuery {
private int startIndex;
private int pageSize;
// 省略getter/setter/构造方法
}
2、持久层接口方法
/*分页查询2*/
List<User> getUserByPage2(PageQuery pageQuery);
3、映射文件
<!--分页查询2-->
<select id="getUserByPage2" resultType="com.zj.pojo.User">
SELECT * FROM user LIMIT #{startIndex},#{pageSize}
</select>
4、测试类
/*分页查询2*/
@Test
public void TestGetUserByPage2(){
List<User> userByPage2 = mapper.getUserByPage2(new PageQuery(0, 3));
for (User user : userByPage2) {
System.out.println(user);
}
}
Map传参
如果不想自定义POJO,可以使用Map作为传递参数的载体,在SQL语句中绑定参数时使用Map的Key作为参数名即可。此方法推荐使用。
1、持久层接口方法
/*分页查询3*/
List<User> getUserByPage3(Map<String,Object> params);
2、映射文件
<!--分页查询3-->
<select id="getUserByPage3" resultType="com.zj.pojo.User" parameterType="map">
SELECT * FROM user LIMIT #{start},#{size}
</select>
3、测试类
/*分页查询3*/
@Test
public void TestGetUserByPage3(){
Map<String, Object> map = new HashMap<>();
map.put("start",0);
map.put("size",3);
List<User> userByPage3 = mapper.getUserByPage3(map);
for (User user : userByPage3) {
System.out.println(user);
}
}
3.6 聚合查询和主键回填
查询用户总数
1、持久层方法
int findCount();
2、映射文件
<!--查询总数-->
<select id="findCount" resultType="int">
select count(id) from user;
</select>
3、测试
/*查询总数*/
@Test
public void TestGFindCount(){
int count = mapper.findCount();
System.out.println(count);
}
主键回填
有时我们需要获取新插入数据的主键值。如果数据库中主键是自增的,这时我们就需要使用MyBatis的主键回填功能。
1、持久层方法
/*主键回填*/
void addUser2 (User user);
2、映射文件
<!--主键回填-->
<insert id="addUser2" parameterType="com.zj.pojo.User" >
/*keyProperty:主键属性名称
keyColumn:主键列名
resultType:主键类型
order:执行时机,插入之后执行*/
<selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
SELECT LAST_INSERT_ID();
</selectKey>
INSERT INTO user VALUES (default ,#{username},#{sex},#{address})
</insert>
SELECT LAST_INSERT_ID():查询刚刚插入的记录的主键值,只适用于自增主键,且必须和insert语句一起执行。
3、测试
/*主键回填*/
@Test
public void TestAddUser2(){
User user = new User(0,"啊张","男","海口市");
mapper.addUser2(user);
/*提交*/
sqlSession.commit();
/*获取回填的id*/
int id = user.getId();
System.out.println(id);//9
}
四、MyBatis配置文件
MyBatis配置文件结构:
-configuration
-properties(属性)
-property
-settings(全局配置参数)
-setting
-plugins(插件)
-plugin
-typeAliases(别名)
-typeAliase
-package
-environments(环境)
-environment
-transactionManager(事务管理)
-dataSource(数据源)
-mappers(映射器)
-mapper
-package
4.1 <properties>
属性值定义。properties标签中可以定义属性值,也可以引入外部配置文件。无论是内部定义还是外部引入,都可以使用${name}获取值。
例如:我们可以将数据源配置写到外部的db.properties中,再使用properties标签引入外部配置文件,这样可以做到动态配置数据源。
1、编写db.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=123456
2、在配置文件中引入db.properties
<!--数据库文件-->
<properties resource="db.properties"></properties>
<!--配置数据源-->
<environments default="mysql">
<environment id="mysql">
<!--事务类型-->
<transactionManager type="JDBC"></transactionManager>
<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>
</environment>
</environments>
4.2 <settings>
<settings>
是配置MyBatis运行时的一些行为的,例如缓存、延迟加载、命名规则等一系列控制性参数。后期我们会使用该标签配置缓存和延迟加载等。
在配置文件中开启二级缓存:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
开启N+1查询的延迟加载:
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
4.3 <typeAliases>
MyBatis对常用类有默认别名支持,比如java.lang.Stirng的别名为string。除此之外,我们也可以使用<typeAliases>
设置自定义别名。
为一个类配置别名
<typeAliases>
<typeAlias type="全类名" alias="别名"></typeAlias>
</typeAliases>
1、配置文件
<!--配置别名-->
<typeAliases>
<typeAlias type="com.zj.pojo.User" alias="User"></typeAlias>
</typeAliases>
2、映射文件
<!--查询全部-->
<select id="selectAllUser" resultType="User">
SELECT * FROM user;
</select>
为一个所有包下的所有类配置别名
<typeAliases>
<package name="包名"></package>
</typeAliases>
此时该包下的所有类都有了别名,别名省略包名,和类名相同。
1、配置文件
<!--配置别名-->
<typeAliases>
<package name="com.zj.pojo"/>
</typeAliases>
2、映射文件
<!--分页查询2-->
<select id="getUserByPage2" resultType="User" parameterType="PageQuery">
SELECT * FROM user LIMIT #{startIndex},#{pageSize}
</select>
4.4 <plugins>
<plugins>
是配置MyBatis插件的。插件可以增强MyBatis功能,比如进行sql增强,打印日志,异常处理等。使用该标签配置分页插件。
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 设置数据库类型-->
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
4.5 <environments>
<environments>
可以为MyBatis配置数据环境。
事务管理
<environments default="mysql">
<environment id="mysql">
<!-- JDBC:使用JDBC的提交和回滚 MANAGED:不做事务处理-->
<transactionManager type="JDBC"></transactionManager>
</environment>
</environments>
连接池
<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自带的连接池管理。
4.6 <mappers>
<mappers>
用于注册映射文件或持久层接口,只有注册的映射文件才能使用,共有四种方式都可以完成注册:
1、使用相对路径注册映射文件
<mappers>
<mapper resource="com/zj/mapper/UserMapper.xml"/>
</mappers>
2、使用绝对路径注册映射文件
<mappers>
<mapper url="D:\Java\code\learnMyBatis\src\main\resources\com\zj\mapper\UserMapper.xml"></mapper>
</mappers>
3、注册持久层接口(映射文件和持久层路径一样,文件名也一样)
<!--注册映射文件(项目加载的时候先加载的是核心配置文件)-->
<mappers>
<mapper class="com.zj.mapper.UserMapper"/>
</mappers>
4、注册一个包下的所有持久层接口
<!--注册映射文件(项目加载的时候先加载的是核心配置文件)-->
<mappers>
<package name="com.zj.mapper"/>
</mappers>
五、MyBatis映射文件
5.1<resultMap>
标签的作用的自定义映射关系。
MyBatis可以将数据库结果集封装到对象中,是因为结果集的列名和对象属性名相同,当POJO属性名和数据库列名不一致时,MyBatis无法自动完成映射关系。
此时有两种解决方案:
-
Sql语句的查询字段起与POJO属性相同的别名。
<!--查询全部老师-->
<select id="findAllTeachers" resultType="Teacher">
SELECT tid,tname as teacherName FROM teacher;
</select>
2、自定义映射关系
- 在映射文件中,使用
<resultMap>
自定义映射关系 - 在
<select>
标签中,使用resultMap
属性代替resultType
属性,使用自定义映射关系。
<!--查询全部老师-->
<resultMap id="findAllTeachersMap" type="Teacher"> <!--type:自定义的映射对象类型-->
<id property="tid" column="tid"/><!--property实体类的属性,column字段名称-->
<result property="teacherName" column="tname"/>
</resultMap>
<select id="findAllTeachers" resultMap="findAllTeachersMap">
SELECT * FROM teacher;
</select>
5.2 <sql>、<include>
<sql>
用来定义可重用的Sql片段,通过<include>
引入该片段。如:Sql语句的查询字段起与POJO属性相同的别名,该Sql片段就可以重用。
<sql id="sql">
tid as tid,tname as teacherName
</sql>
<!--根据id查询老师-->
<select id="findTeacherById" parameterType="int" resultType="Teacher">
SELECT<include refid="sql"/> FROM teacher WHERE tid = #{tid}
</select>
5.3 特殊字符处理
在Mybatis映射文件中尽量不要使用一些特殊字符,如:<
,>
等。
我们可以使用符号的实体来表示:
符号 | 实体 |
---|---|
< | < |
> | > |
& | & |
‘ | ' |
“ | " |
<!--查询id大于某个值的老师-->
<select id="findTeachersById2" parameterType="int" resultType="Teacher">
SELECT <include refid="sql"/> FROM teacher WHERE tid > #{tid}
</select>
六、MyBatis动态Sql
6.1 <if>
一个查询的方法的Sql语句不一定是固定的。比如电商网站的查询商品,用户使用不同条件查询,Sql语句就会添加不同的查询条件。此时就需要在方法中使用动态Sql语句。
<if>
标签内的Sql片段在满足条件后才会添加,用法为:<if test="条件">
。例如:根据不同条件查询用户:
1、持久层接口添加方法
/*通用的用户查询*/
List<User> findByCondition(User user);
2、映射文件
<select id="findByCondition" parameterType="User" resultType="User">
SELECT * FROM user where 1 = 1
<if test="username != null and username.length() != 0">
and username like #{username}
</if>
<if test="sex != null and sex.length() != 0">
and sex = #{sex}
</if>
<if test="address != null and address.length() != 0">
and address = #{address}
</if>
</select>
3、测试
@Test
public void TestFindByCondition(){
User user = new User();
user.setUsername("%张%");
user.setSex("男");
List<User> byCondition = mapper.findByCondition(user);
for (User user1 : byCondition) {
System.out.println(user1);
}
}
if中的条件不能使用&&/||,而应该使用and/or
if中的条件可以直接通过属性名获取参数POJO的属性值,并且该值可以调用方法。
where后为什么要加1=1?
任意条件都可能拼接到Sql中。如果有多个条件,从第二个条件开始前都需要加And关键字。加上1=1这个永久成立的条件,就不需要考虑后面的条件哪个是第一个条件,后面的条件前都加And关键字即可。
6.2 <where>、<set>
<where>
可以代替sql中的where 1=1 和第一个and,更符合程序员的开发习惯,使用<where>
后的映射文件如下:
<update id="UpdateUser" parameterType="User">
UPDATE user
<set>
<if test="username != null and username.length() != 0">
username = #{username},
</if>
<if test="sex != null and sex.length() != 0">
sex = #{sex},
</if>
<if test="address != null and address.length() != 0">
address = #{address},
</if>
</set>
<where>
id = #{id}
</where>
</update>
<set>
标签用在update语句中。借助<if>
,可以只对有具体值的字段进行更新。<set>
会自动添加set关键字,并去掉最后一个if语句中多余的逗号。
<update id="UpdateUser" parameterType="User">
UPDATE user
<set>
<if test="username != null and username.length() != 0">
username = #{username},
</if>
<if test="sex != null and sex.length() != 0">
sex = #{sex},
</if>
<if test="address != null and address.length() != 0">
address = #{address},
</if>
</set>
<where>
id = #{id}
</where>
</update>
6.3 <choose>、<when>、<otherwise>
- chose:父标签
- when:相当于if...else if,只要有一个条件成立,其它的都不判断了
- otherwise:相当于else,若所有条件都不成立,则执行otherwise
- when至少设置一个,otherwise最多设置一个
1、持久层
/*根据用户名查询用户:
* 参数长度小于5使用模糊查询
* 参数长度5~10使用精确查询
* 否则返回id为1的用户*/
List<User> findUserByName(String username);
2、映射文件
<!--根据用户名查询用户-->
<select id="findUserByName" parameterType="string" resultType="User">
SELECT * FROM user
<where>
<choose>
<when test="username.length < 5">
<bind name="likeName" value="'%'+username+'%'"/>
username like #{likeName}
</when>
<when test="username.length < 10">
username = #{username}
</when>
<otherwise>
id = 1;
</otherwise>
</choose>
</where>
</select>
6.4 foreach遍历数组
<foreach>
类似JAVA中的for循环,可以遍历集合或数组。<foreach>
有如下属性:
- collection:遍历的对象类型
- open:开始的sql语句
- close:结束的sql语句
- separator:遍历每项间的分隔符
- item:表示本次遍历获取的元素,遍历List、Set、数组时表示每项元素,遍历map时表示键值对的值。
- index:遍历List、数组时表示遍历的索引,遍历map时表示键值对的键。
我们使用<foreach>
遍历数组进行批量删除。
1、持久层接口添加方法
/*批量删除*/
void deleteBatch(int[] ids);
2、映射文件
<!--批量删除-->
<delete id="deleteBatch" parameterType="int">
delete from user
<where>
<foreach open="id in (" collection="array" item="id" separator="," close=")">
#{id}
</foreach>
</where>
</delete>
3、测试
@Test
public void TestDeleteBatch(){
int[] batch = {1,2,3};
mapper.deleteBatch(batch);
sqlSession.commit();
}
6.5 foreach遍历Collection
<foreach>
遍历List和Set的方法是一样的,我们使用<foreach>
遍历List进行批量添加。
1、持久层接口添加方法
/*批量添加*/
void insertBatch(List<User> users);
2、映射文件
<!--批量添加-->
<insert id="insertBatch" parameterType="User">
insert into user values
<foreach collection="list" item="user" separator=",">
(default ,#{user.username},#{user.sex},#{user.address})
</foreach>
</insert>
3、测试
@Test
public void TestInsertBatch(){
User user1 = new User(0,"顾田然","男","北京市");
User user2 = new User(0,"孔丹秋","女","沈阳市");
User user3 = new User(0,"唐宛凝","女","石家庄市");
User user4 = new User(0,"陈波涛","男","牡丹江市");
List<User> list = new ArrayList<>();
list.add(user1);
list.add(user2);
list.add(user3);
list.add(user4);
mapper.insertBatch(list);
sqlSession.commit();
}
6.6 foreach遍历Map
我们使用<foreach>
遍历Map进行多条件查询。
1、持久层接口添加方法
/**
* 多条件查询
* @param map 查询的条件键值对 键:属性名 值:属性值
* @return
*/
List<User> findUser(@Param("queryMap") Map<String,Object> map);
2、映射文件
<!--多条件查询-->
<select id="findUser" parameterType="map" resultType="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> map = new HashMap<>();
map.put("sex","男");
map.put("address","北京市");
List<User> users = mapper.findUser(map);
for (User user : users) {
System.out.println(user);
}
}
七、MyBatis缓存
7.1 缓存介绍
缓存是内存当中一块存储数据的区域,目的是提高查询效率。MyBatis会将查询结果存储在缓存当中,当下次执行相同的SQL时不访问数据库,而是直接从缓存中获取结果,从而减少服务器的压力。
什么是缓存?
存在于内存中的一块数据。
缓存有什么作用?
减少程序和数据库的交互,提高查询效率,降低服务器和数据库的压力。
什么样的数据使用缓存?
经常查询但不常改变的,改变后对结果影响不大的数据。
MyBatis缓存分为哪几类?
一级缓存和二级缓存
如何判断两次Sql是相同的?
- 查询的Sql语句相同
- 传递的参数值相同
- 对结果集的要求相同
- 预编译的模板Id相同
7.2 一级缓存
-
MyBatis一级缓存也叫本地缓存。SqlSession对象中包含一个Executor对象,Executor对象中包含一个PerpetualCache对象,在该对象存放一级缓存数据。
-
由于一级缓存是在SqlSession对象中,所以只有使用同一个SqlSession对象操作数据库时才能共享一级缓存。
-
MyBatis的一级缓存是默认开启的,不需要任何的配置。
7.3 清除一级缓存
进行以下操作可以清空MyBatis一级缓存:
SqlSession
调用close()
:操作后SqlSession对象不可用,该对象的缓存数据也不可用。SqlSession
调用clearCache()
/commit()
:操作会清空一级缓存数据。SqlSession
调用增删改方法:操作会清空一级缓存数据,因为增删改后数据库发生改变,缓存数据将不准确。
7.4 MyBatis二级缓存
-
MyBatis二级缓存也叫全局缓存。数据存放在SqlSessionFactory中,只要是同一个工厂对象创建的SqlSession,在进行查询时都能共享数据。一般在项目中只有一个SqlSessionFactory对象,所以二级缓存的数据是全项目共享的。
-
MyBatis一级缓存存放的是对象,二级缓存存放的是对象的数据。所以要求二级缓存存放的POJO必须是可序列化的,也就是要实现Serializable接口。
-
MyBatis二级缓存默认不开启,手动开启后数据先存放在一级缓存中,只有一级缓存数据清空后,数据才会存到二级缓存中。
SqlSession
调用clearCache()
无法将数据存到二级缓存中。
开启二级缓存
1、POJO类实现Serializable接口。
public class User implements Serializable {
private int id;
private String username;
private String sex;
private String address;
}
2、在MyBatis配置文件添加如下设置:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
由于cacheEnabled默认值是true,所以该设置可以省略。
3、在映射文件添加<cache />
标签,该映射文件下的所有方法都支持二级缓存。
<cache size="2048"/>
如果查询到的集合中对象过多,二级缓存只能缓存1024个对象引用。可以通过
<cache />
标签的size属性修改该数量。
八、MyBatis关联查询
MyBatis的关联查询分为一对一关联查询和一对多关联查询。
- 查询对象时,将关联的另一个对象查询出来,就是一对一关联查询。
- 查询对象时,将关联的另一个对象的集合查询出来,就是一对多关联查询。
例如有学生类和班级类:
一个学生对应一个班级,也就是学生类中有一个班级属性,这就是一对一关系。
一个班级对应多个学生,也就是班级类中有一个学生集合属性,这就是一对多关系。
实体类设计如下:
public class Student {
private int sid;
private String name;
private int age;
private String sex;
private Classes classes;
// 省略getter/setter/toString
}
public class Classes {
private int cid;
private String className;
private List<Student> studentList;
// 省略getter/setter/toString
}
数据库设计如下:
8.1 一对一关联查询
查询学生时,将关联的一个班级对象查询出来,就是一对一关联查询。
1、创建持久层接口
List<Student> findAllStudent();
2、映射文件
<!--自定义映射关系-->
<resultMap id="findAllStudentMap" type="Student">
<id property="sid" column="sid"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<!--一对一映射
property:属性名;column:关联列名(两张表通过哪个字段相关联);javaType:对象类型
-->
<association property="classes" column="cid" javaType="Classes">
<id property="cid" column="cid"/>
<result property="className" column="className"/>
</association>
</resultMap>
<select id="findAllStudent" resultMap="findAllStudentMap" >
select * from student left join classes on student.classId = classes.cid;
</select>
3、测试
@Test
public void testFindAllStudent() {
List<Student> allStudent = studentMapper.findAllStudent();
for (Student student : allStudent) {
System.out.println(student);
}
}
8.2 一对多关联查询
查询班级时,将关联的学生集合查询出来,就是一对多关联查询。
1、持久层
List<Classes> finAllClasses();
2、映射文件
<!--一对多查询-->
<resultMap id="finAllClassesMap" type="Classes">
<id property="cid" column="cid"/>
<result property="className" column="className"/>
<!--
一对多关联映射
property:属性名;column:关联列;ofType:集合泛型
-->
<collection property="studentList" column="classId" ofType="Student">
<id property="sid" column="sid"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
</collection>
</resultMap>
<select id="finAllClasses" resultMap="finAllClassesMap">
select * from classes left join student on classses.cid = student.classId
</select>
3、测试
@Test
public void testFindAllClasses() {
List<Classes> classes = classesMapper.finAllClasses();
for (Classes aClass : classes) {
System.out.println(aClass);
}
}
8.3多对多关系映射
MyBatis多对多关联查询本质就是两个一对多关联查询。
例如有老师类和班级类:
一个老师对应多个班级,也就是老师类中有一个班级集合属性。
一个班级对应多个老师,也就是班级类中有一个老师集合属性。
实体类设计如下:
public class Teacher {
private Integer tid;
private String tname;
private List<Classes> classes;
// 省略getter/setter/toString
}
public class Classes {
private Integer cid;
private String className;
private List<Student> studentList;
private List<Teacher> teacherList;
// 省略getter/setter/toString
}
在数据库设计中,需要建立中间表,双方与中间表均为一对多关系。
1、持久层
List<Teacher> findAllTeachers();
2、映射文件
<!--多对多关联-->
<resultMap id="findAllTeachersMap" type="Teacher">
<id property="tid" column="tid"/>
<result property="tname" column="tname"/>
<!--column:主表的关联键-->
<collection property="classesList" column="tid" ofType="Classes">
<id property="cid" column="cid"/>
<result property="className" column="className"/>
</collection>
</resultMap>
<select id="findAllTeachers" resultMap="findAllTeachersMap" >
select * from teacher
left join classes_teacher on teacher.tid = classes_teacher.tid
left join classes on classes_teacher.cid = classes.cid
</select>
3、测试
@Test
public void testFindAllTeachers() {
List<Teacher> allTeachers = teacherMapper.findAllTeachers();
for (Teacher allTeacher : allTeachers) {
System.out.println(allTeacher);
}
}
九、MyBatis分解式查询
在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查询:
- 优点:结果集分步获取,节省内存空间。
- 缺点:由于需要执行多次查询,相比连接查询效率低。
9.1 一对多分解式查询
我们以查询班级时关联查询出学生为例,使用N+1查询:
1、持久层
public interface ClassesMapper {
// 查询所有班级
List<Classes> findAll();
}
public interface StudentMapper {
// 根据班级Id查询学生
List<Student> findByClassId(int classId);
}
2、映射文件
<!--分解查询-->
<resultMap id="findAllClasses2Map" type="Classes">
<id property="cid" column="cid"/>
<result property="className" column="className"/>
<!--调用从表的查询方法.column:将哪个列的值作为select语句的参数-->
<collection property="studentList" ofType="Student" select="com.zj.mapper.StudentMapper.findByClassId" column="cid"/>
</resultMap>
<select id="findAllClasses2" resultMap="findAllClasses2Map">
SELECT * from classes;
</select>
<!--分解查询,根据classId查学生-->
<select id="findByClassId" parameterType="int" resultType="Student">
SELECT * from student where classId = #{classId}
</select>
3、测试
/*分解查询*/
@Test
public void testFindAllClasses2(){
List<Classes> allClasses2 = classesMapper.findAllClasses2();
for (Classes classes : allClasses2) {
System.out.println(classes);
}
}
9.2 一对一分解式查询
根据学生id查询学生的信息,查询学生时关联查询出班级也可以使用分解式查询,首先将查询语句分开:
select * from student;
select * from classes where cid = ?;
1、持久层
public interface StudentMapper {
// 查询所有学生
List<Student> findAll();
}
public interface ClassesMapper {
// 根据ID查询班级
Classes findByCid(int cid);
}
2、映射文件
<!--分解式查询学生和学生所在的班级的信息-->
<resultMap id="findAllStudent2Map" type="Student">
<id property="sid" column="sid"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<association property="classes" select="com.zj.mapper.ClassesMapper.findClassById" column="classId"/>
</resultMap>
<select id="findAllStudent2" resultMap="findAllStudent2Map">
select * from student;
</select>
<!--分解查询根据班级id查询班级信息-->
<select id="findClassById" parameterType="int" resultType="Classes">
select * from classes where cid = #{cid};
</select>
3、测试
@Test
public void testFindAllStudent() {
List<Student> allStudent = studentMapper.findAllStudent();
for (Student student : allStudent) {
System.out.println(student);
}
}
9.3 延迟加载
分解式查询又分为两种加载方式:
- 立即加载:在查询主表时就执行所有的Sql语句。
- 延迟加载:又叫懒加载,首先执行主表的查询语句,使用从表数据时才触发从表的查询语句。
延迟加载在获取关联数据时速度较慢,但可以节约资源,即用即取。
开启延迟加载
设置所有的N+1查询都为延迟加载:
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
-
设置某个方法为延迟加载:
在
<association>
、<collection>
中添加fetchType属性设置加载方式。lazy:延迟加载;eager:立即加载。
测试延迟加载
十、MyBatis注解开发
-
创建maven工程,引入依赖
-
创建mybatis核心配置文件SqlMapConfig.xml
-
将log4j.properties文件放入resources中,让控制台打印SQL语句。
-
创建实体类
-
创建持久层接口,并在接口方法上定义Sql语句
public interface UserMapper {
@Select("select * from user")
List<User> findAll();
}
由于注解在方法上方,而方法中就有参数类型和返回值类型,所以使用注解开发不需要定义参数类型和返回值类型
6、在核心配置文件注册持久层接口,由于没有映射文件,所以只能采用注册接口或注册包的方法。
<!--基于注解开发的话,没有映射文件就注册接口-->
<mappers>
<package name="com.zj.mapper"/>
</mappers>
7、测试方法
@Test
public void testFindAllUsers(){
List<User> allUsers = userMapper.findAllUsers();
for (User allUser : allUsers) {
System.out.println(allUser);
}
}
10.1 基于注解实现增删改查
@Select("select * from user")
List<User> findAllUsers();
@SelectKey(keyColumn = "id",keyProperty = "id",resultType = int.class,before = false,
statement = "select LAST_INSERT_ID()")
@Insert("insert into user values (default, #{username}, #{sex},#{address})")
void addUser(User user);
@Delete("delete from user where id = #{id}")
void removeUser(int id);
@Update("update user set username = #{username}, sex = #{sex}, address = #{address}" +
"where id = #{id}")
void updateUser(User user);
@Select("select * from user where username like #{username}")
List<User> findUserLike(String username);
10.2 动态sql
MyBatis注解开发中有两种方式构建动态Sql:
使用脚本标签(了解即可千万别用,又臭又长)
将Sql嵌套在<script>
内即可使用动态Sql标签:
// 根据任意条件查询
@Select("<script>" +
" select * from user\n" +
" <where>\n" +
" <if test=\"username != null and username.length() != 0\">\n" +
" username like #{username}\n" +
" </if>\n" +
" <if test=\"sex != null and sex.length() != 0\">\n" +
" and sex = #{sex}\n" +
" </if>\n" +
" <if test=\"address != null and address.length() != 0\">\n" +
" and address = #{address}\n" +
" </if>\n" +
" </where>" +
"</script>")
List<User> findByCondition(User user);
在方法中构建动态Sql
在MyBatis中有@SelectProvider
、@UpdateProvider
、@DeleteProvider
、@InsertProvider
注解。当使用这些注解时将不在注解中直接编写SQL,而是调用某个类的方法来生成SQL。
1、创建类构建sql语句
package com.zj.provider;
import com.zj.pojo.User;
/*构建sql*/
public class UserProvider {
//生成根据任意条件查询的sql语句
public String findByConditionSql(User user){
StringBuffer sb = new StringBuffer("SELECT * FROM user WHERE 1=1");
if (user.getUsername() != null && user.getUsername().length() > 0){
sb.append(" and username = #{username}");
}
if (user.getSex() != null && user.getSex().length() > 0){
sb.append(" and sex = #{sex}");
}
if (user.getAddress() != null && user.getAddress().length() > 0){
sb.append(" and address = #{address}");
}
return sb.toString();
}
}
2、持久层
/*根据任意给定条件查询用户,使用某个类构建sql*/
@SelectProvider(type = UserProvider.class,method = "findByConditionSql")
List<User> findByConditionSql(User user);
3、测试
@Test
public void testFindByConditionSql(){
User user = new User();
user.setUsername("顾田然");
List<User> users = userMapper.findByConditionSql(user);
for (User user1 : users) {
System.out.println(user1);
}
}
10.3 自定义映射
当POJO属性名与数据库列名不一致时,需要自定义实体类和结果集的映射关系,在MyBatis注解开发中,使用@Results
定义并使用自定义映射,使用@ResultMap
使用自定义映射,用法如下:
/*自定义映射*/
@Results(id = "userMapper",value = {
@Result(id = true,property = "id",column = "id"),
@Result(property = "username",column = "username"),
@Result(property = "sex",column = "sex"),
@Result(property = "address",column = "address")
})
@Select("select * from user")
List<User> findAllUsers();
10.4 基于注解开启二级缓存
1、POJO类实现Serializable接口。
2、在持久层接口上方加注解@CacheNamespace(blocking=true),该接口的所有方法都支持二级缓存。
@CacheNamespace(blocking = true)
public interface UserMapper {
……
}
10.5 基于注解实现一对一关联查询
在MyBatis的注解开发中对于多表查询只支持分解查询(N+1),不支持连接查询。
1、创建实体类
public class Student {
private int sid;
private String name;
private int age;
private String sex;
private Classes classes;
// 省略getter/setter/toString
}
public class Classes {
private int cid;
private String className;
private List<Student> students;
// 省略getter/setter/toString
}
2、持久层
package com.zj.mapper;
import com.zj.pojo.Student;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.mapping.FetchType;
import java.util.List;
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"),
//调用查询时传入的参数是哪列
@Result(property = "classes",column = "classId",
one = @One(select = "com.zj.mapper.ClassesMapper.findClassById",
fetchType = FetchType.EAGER)
)
})
@Select("select * from Student")
List<Student> findAllStudent();
}
package com.zj.mapper;
import com.zj.pojo.Classes;
import org.apache.ibatis.annotations.Select;
public interface ClassesMapper {
//根据班级cid查询班级信息
@Select("select * from classes where cid = #{cid}")
Classes findClassById(int cid);;
}
3、测试
@Test
public void testStudentMapper() {
List<Student> allStudent = studentMapper.findAllStudent();
for (Student student : allStudent) {
System.out.println(student);
}
}
10.6 基于注解实现一对多关联查询
查询班级信息的时候将该班级下的学生一起查出来。
1、持久层
//查询全部班级(一对多一般使用懒加载)
@Results(id = "classesMap",value = {
@Result(id = true,property = "cid",column = "cid"),
@Result(property = "className",column = "className"),
@Result(property = "studentList",column = "cid",
many = @Many(select="com.zj.mapper.StudentMapper.findStudentByCid",
fetchType = FetchType.LAZY))
})
@Select("select * from classes")
List<Classes> findAllClasses();
//根据班级id查询学生
@Select("select * from student where classId = #{classId}")
Student findStudentByCid(int classId);
2、测试
@Test
public void testFindAllClasses(){
List<Classes> allClasses = classesMapper.findAllClasses();
for (Classes allClass : allClasses) {
System.out.println(allClass);
}
}
10.7 注解和映射文件对比
MyBatis中更推荐使用映射文件开发,Spring、SpringBoot更推荐注解方式。具体使用要视项目情况而定。它们的优点对比如下:
映射文件:
- 代码与Sql语句是解耦的,修改时只需修改配置文件,无需修改源码。
- Sql语句集中,利于快速了解和维护项目。
- 级联查询支持连接查询和分解查询两种方式,注解开发只支持分解查询。
注解:
- 配置简单,开发效率高。
- 类型安全,在编译期即可进行校验,不用等到运行时才发现错误。
十一、PageHelper分页插件
开发过程中如果要进行分页查询,需要传入页数和每页条数。返回页面数据,总条数,总页数,当前页面,每页条数等数据。此时使用PageHelper插件可以快速帮助我们获取这些数据。
PageHelper是一款非常好用的开源免费的Mybatis第三方分页插件。使用该插件时,只要传入分页参数,即可自动生成页面对象。我们使用该插件分页查询所有用户:
1、引入依赖
<!-- PageHelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.0</version>
</dependency>
2、Mybatis配置文件中配置PageHelper插件
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 设置数据库类型-->
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
3、测试
@Test
public void testFindAllUsers(){
//1、设置分页参数
Page<Object> objects = PageHelper.startPage(1, 3);
//查询数据库
List<User> allUsers = userMapper.findAllUsers();
//封装查询结果生成页面对象
PageInfo<User> pageInfo = new PageInfo(allUsers);
//打印页面数据
List<User> list = pageInfo.getList();
System.out.println("结果集:"+list);
System.out.println("总条数:"+pageInfo.getTotal());
System.out.println("总页数:"+pageInfo.getPages());
System.out.println("当前页:"+pageInfo.getPageNum());
System.out.println("每页条数:"+pageInfo.getSize());
}
十二、MyBatis Generator
MyBatis Generator(MBG)是MyBatis官方提供的代码生成器。它可以根据数据库的表结构自动生成POJO类、持久层接口与映射文件,极大减少了代码的编写量,提高开发效率。
MBG可以作为项目引入使用,也可以作为Maven插件使用,其中作为Maven插件使用更加方便快捷。
1、准备数据库表
2、在pom文件中配置MBG插件
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.4.2</version>
<!--MBG配置-->
<configuration>
<!--配置文件的位置-->
<configurationFile>src/main/resources/generatorConfig.xml</configurationFile>
<!--运行显示详情-->
<verbose>true</verbose>
<!--允许文件覆盖-->
<overwrite>true</overwrite>
</configuration>
</plugin>
</plugins>
</build>
3、编写MBG配置文件
<?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="D:\Java\apache-maven-3.8.3\repositories\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.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis"
userId="root"
password="123456">
</jdbcConnection>
<!-- 类型处理器,在数据库类型和java类型之间的转换控制-->
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!-- targetProject:POJO类路径 targetProject:生成的POJO类的包(需要提前创建包)-->
<javaModelGenerator targetProject="src/main/java" targetPackage="com.zj.pojo">
<!-- 是否生成子包 -->
<property name="enableSubPackages" value="false"/>
<!-- 设置是否在getter方法中,对String类型字段调用trim()方法 -->
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!-- targetProject:配置文件路径 targetPackage:生成映射文件的位置(要提前创建文件目录) -->
<sqlMapGenerator targetProject="src/main/resources" targetPackage="com.zj.mapper">
<!-- 是否生成子包 -->
<property name="enableSubPackages" value="false"/>
</sqlMapGenerator>
<!-- targetPackage:JAVA类路径 targetProject:生成的持久层接口包 -->
<javaClientGenerator targetProject="src/main/java" targetPackage="com.zj.mapper" type="XMLMAPPER">
<!-- 是否生成子包 -->
<property name="enableSubPackages" value="false"/>
</javaClientGenerator>
<!-- 数据库表(根据哪些表生成,可以写多个table标签),表名不要和其他库中的表名一样 -->
<table tableName="product"></table>
</context>
</generatorConfiguration>
4、双击运行插件,自动生成POJO,持久层接口,映射文件:
Product.java:POJO类
ProductMapper.java:持久层接口
ProductMapper.xml:映射文件
ProductExample.java:查询扩展类,该类可以构造复杂的查询条件。
- Criterion:代表一个字段。
- GeneratedCriteria:抽象类,生成查询条件的工具。
- Criteria:GeneratedCriteria的子类,生成查询条件的工具。
12.1 增删改方法
注意在生成插件后还要搭建mybatis,再添加上配置文件。
//添加
@Test
public void testAdd(){
Product product = new Product(0,"英特尔(Intel) i7-13700K 13代 酷睿 处理器",2999.0);
int insert = productMapper.insert(product);
sqlSession.commit();
}
//修改
@Test
public void testUpdate(){
Product product = new Product();
product.setId(3);
product.setProductname("小天才电话手表");
product.setPrice(499.0);
int i = productMapper.updateByPrimaryKey(product);
sqlSession.commit();
}
//删除
@Test
public void testDelete(){
int i = productMapper.deleteByPrimaryKey(2);
sqlSession.commit();
}
12.2 查询方法
//根据id查询
@Test
public void testFindById(){
Product product = productMapper.selectByPrimaryKey(1);
System.out.println(product);
}
//查询全部
@Test
public void testFindAll(){
/*除了根据id查询之外其他的查询方法都需要使用查询扩展对象来构建查询条件*/
ProductExample productExample = new ProductExample();
/*查询全部不需要构建查询条件*/
List<Product> products = productMapper.selectByExample(productExample);
for (Product product : products) {
System.out.println(product);
}
}
//根据名称查询
@Test
public void testFindByName(){
/*构建查询条件*/
ProductExample productExample = new ProductExample();
ProductExample.Criteria criteria = productExample.createCriteria();
criteria.andProductnameLike("%华为Mate40%");
/*查询*/
List<Product> products = productMapper.selectByExample(productExample);
for (Product product : products) {
System.out.println(product);
}
}
12.3复杂查询
//多条件的and查询
@Test
public void testFindAnd(){
/*构建查询条件*/
ProductExample productExample = new ProductExample();
ProductExample.Criteria criteria = productExample.createCriteria();
criteria.andProductnameLike("%华为%");
criteria.andPriceBetween(3000.0,10000.0);
List<Product> products = productMapper.selectByExample(productExample);
for (Product product : products) {
System.out.println(product);
}
}
//多条件的or查询
@Test
public void testFindOr(){
/*构建查询条件1*/
ProductExample productExample = new ProductExample();
ProductExample.Criteria criteria1 = productExample.createCriteria();
criteria1.andProductnameLike("%华为%");
/*构建查询条件2*/
ProductExample.Criteria criteria2 = productExample.createCriteria();
criteria2.andPriceBetween(8000.0,10000.0);
/*创建or关系(括号中只放第二个)*/
productExample.or(criteria2);
/*查询*/
List<Product> products = productMapper.selectByExample(productExample);
for (Product product : products) {
System.out.println(product);
}
}