1、Mybatis概述
1.1 Mybatis概念
MyBatis 本是 apache 的一个开源项目 iBatis, 2010 年这个项目由 apache software foundation 迁移到了 google code,并且改名为MyBatis 。2013 年 11 月迁移到 Github。iBATIS 一词来源于“internet”和“abatis”的组合,是一个基于 Java 的持久层框架。iBATIS 提供的持久层框架包括 SQL Maps 和 Data Access Objects(DAO)。
Mybatis 基于java的持久层框架,它的内部封装了JDBC,让开发人员只需要关注SQL语句本身,不需要花费精力在驱动的加载、连接的创建、Statement的创建等复杂的过程。
Mybatis通过XML或注解的方式将要执行的各种的statement配置起来,并通过java对象和statement中的sql的动态参数进行映射生成最终执行的SQL语句,最后由mybatis框架执行SQL,并将结果直接映射为java对象。
采用了ORM思想解决了实体类和数据库表映射的问题。对JDBC进行了封装,屏蔽了JDBCAPI底层的访问细节,避免我们与jdbc的api打交道,就能完成对数据的持久化操作。
- O–Object java对象
- R- Relation 关系,就是数据库中的一张表
- M-mapping 映射
1.2 JDBC编程
public class TestJDBC {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
String url="jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT";
conn= DriverManager.getConnection(url,"root","root");
String sql="select * from team;";
ps=conn.prepareStatement(sql);
rs = ps.executeQuery();
List<Team> list=new ArrayList<>();
while (rs.next()){
Team team=new Team();
team.setTeamName(rs.getString("teamName"));
team.setTeamId(rs.getInt("teamId"));
team.setCreateTime(rs.getDate("createTime"));
team.setLocation(rs.getString("location"));
list.add(team);
}
list.forEach(team -> System.out.println(team));
} catch (Exception e){
e.printStackTrace();
} finally {
try {
if (rs != null){
rs.close();
}
if (ps != null){
ps.close();
}
if (conn != null){
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
1.3 Mybatis解决的问题
- 数据库连接的创建、释放连接的频繁操作造成资源的浪费从而影响系统的性能。
- SQL语句编写在代码中,硬编码造成代码不容易维护,实际应用中SQL语句变化的可能性比较大,一旦变动就需要改变java类。
- 使用preparedStatement的时候传递参数使用占位符,也存在硬编码,因为SQL语句变化,必须修改源码。
- 对结果集的解析中也存在硬编码。
2、Mybatis入门案例
2.1 创建数据库和表
CREATE TABLE `team` (
`teamId` int NOT NULL AUTO_INCREMENT COMMENT '球队ID',
`teamName` varchar(50) DEFAULT NULL COMMENT '球队名称',
`location` varchar(50) DEFAULT NULL COMMENT '球队位置',
`createTime` date DEFAULT NULL COMMENT '球队建立时间',
PRIMARY KEY (`teamId`)
) ENGINE=InnoDB AUTO_INCREMENT=1003 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
2.2 创建maven项目,添加Mybatis的jar依赖
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
2.3 编写Mybatis的配置文件
一般情况下:配置文件的名称可以自定义,课程中使用mybatis.xml。配置文件放置在java/resources中。
头文件去官网中复制粘贴。在这里给大家提供一个中文的网站。Mybatis网址
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis? useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
</configuration>
2.4 编写实体类
实体类中的属性必须与表中的列名保持一致
public class Team {
private Integer teamId;
private String teamName;
private String location;
private Date createTime;
private List<Player> playerList1;
private List<Player> playerList2;
@Override
public String toString() {
return "Team{" +
"teamId=" + teamId +
", teamName='" + teamName + '\'' +
", location='" + location + '\'' +
", createTime=" + createTime +
", playerList1=" + playerList1 +
", playerList2=" + playerList2 +
'}';
}
}
2.5 编写ORM映射文件
我们是针对实体类Team.java和表Team进行ORM映射。
Mybatis框架中,ORM映射是针对SQL语句进行,Mybatis框架将SQL语句抽取到了XML中。所以我们需要针对每个实体类编写XML映射文件。
2.5.1 XML映射文件必须与实体类在同一个包下面
2.5.2 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">
<mapper namespace="com.kkb.pojo.Team">
<select id="queryAll" resultType="com.kkb.pojo.Team">
select * from team;
</select>
</mapper>
2.6 将映射文件注册到mybatis的配置文件中
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<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>
<mappers>
<mapper resource="com/kkb/pojo/Team.xml"/>
</mappers>
</configuration>
2.7 配置映射文件的扫描位置
pom.xml文件配置映射文件的扫描路径
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
<plugins>
//省略
</plugins>
</build>
2.8 使用Mybatis框架的核心接口测试
public class TestTeam {
private String resource="mybatis.xml";
@Test
public void test01(){
SqlSession sqlSession=null;
try {
Reader reader = Resources.getResourceAsReader(resource);
SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(reader);
sqlSession= factory.openSession();
List<Team> list = sqlSession.selectList("com.kkb.pojo.Team.queryAll");
for (Team team : list) {
System.out.println(team);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
sqlSession.close();
}
}
}
2.9入门案例的增删改查
2.9.1 根据ID查询单个对象
Team.xml的映射文件中添加:
<select id="queryById" parameterType="java.lang.Integer" resultType="com.kkb.pojo.Team">
select * from team where teamId=#{id}
</select>
测试类中添加如下内容:
@Test
public void testFindById(){
System.out.println("testFindById---------");
Team team = sqlSession.selectOne("com.kkb.pojo.Team.queryById", 1001);
System.out.println(team);
}
@Before
public void before(){
Reader reader = null;
try {
reader = Resources.getResourceAsReader(resource);
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(reader);
sqlSession= factory.openSession();
}
@After
public void after(){
sqlSession.close();
}
2.9.2 增删改
Team.xml的映射文件中添加:
<delete id="del" >
delete from team where teamId=#{id}
</delete>
<update id="update" parameterType="com.kkb.pojo.Team">
update team set teamName=#{teamName},location=#{location}
where teamId=#{teamId}
</update>
<insert id="add" parameterType="com.kkb.pojo.Team" >
INSERT INTO `team` (`teamName`, `location`, `createTime`)
VALUES (#{teamName}, #{location}, #{createTime})
</insert>
测试类中添加如下方法:
@Test
public void testDel(){
int num = sqlSession.delete("com.kkb.pojo.Team.del", 1054);
sqlSession.commit();
System.out.println(num);
}
@Test
public void testUpdate(){
Team team=sqlSession.selectOne("com.kkb.pojo.Team.queryById",1053);
team.setTeamName("dengzeyang的球队");
team.setLocation("不来梅");
int num = sqlSession.update("com.kkb.pojo.Team.update", team);
sqlSession.commit();
System.out.println(num);
}
@Test
public void testAdd(){
Team team=new Team();
team.setTeamName("上海bilibili");
team.setLocation("上海");
team.setCreateTime(new Date());
int num = sqlSession.insert("com.kkb.pojo.Team.add", team);
sqlSession.commit();
System.out.println(num);
}
3、Mybatis对象分析
3.1 Resources
Resources 类,顾名思义就是资源,用于读取资源文件。其有很多方法通过加载并解析资源文件,返回不同类型的 IO 流对象。
3.2 SqlSessionFactoryBuilder
SqlSessionFactory 的 创 建 , 需 要 使 用 SqlSessionFactoryBuilder 对 象 的 build() 方 法 。 事实上使用SqlSessionFactoryBuilder的原因是将SqlSessionFactory这个复杂对象的创建交由Builder来执行,也就是使用了建造者设计模式。
建造者模式: 又称生成器模式,是一种对象的创建模式。 可以将一个产品的内部表象与产品的生成过程分割开来, 从而可以使一个建造过程生成具有 不同的内部表象的产品(将一个复杂对象的构建与它的表示分离, 使得同样的构建过程可以创建不同的表示). 这样用户只需指定需要建造的类型就可 以得到具体产品,而不需要了解具体的建造过程和细节. 在建造者模式中,角色分指导者(Director)与建造者(Builder): 用户联系指导者, 指导者指挥建造者, 最后得到产品. 建造者模式可以强制实行 一种分步骤进行的建造过程.
3.3 SqlSessionFactory
SqlSessionFactory 接口对象是一个重量级对象(系统开销大的对象),是线程安全的,所以一个应用只需要一个该对象即可。创建SqlSession 需要使用 SqlSessionFactory 接口的的 openSession()方法。
默认的 openSession()方法没有参数,它会创建有如下特性的 SqlSession:
- 会开启一个事务(也就是不自动提交)。
- 将从由当前环境配置的 DataSource 实例中获取 Connection 对象。事务隔离级别将会使用驱动或数据源的默认设置。
- 预处理语句不会被复用,也不会批量处理更新。
openSession(true):创建一个有自动提交功能的 SqlSession openSession(false):创建一个非自动提交功能的 SqlSession,需手动提交 openSession():同 openSession(false)
3.4 SqlSession
SqlSession 接口对象用于执行持久化操作。一个 SqlSession 对应着一次数据库会话,一次会话以SqlSession 对象的创建开始,以SqlSession 对象的关闭结束。
SqlSession 接口对象是线程不安全的,所以每次数据库会话结束前,需要马上调用其 close()方法,将其关闭。再次需要会话,再次创建。SqlSession 在方法内部创建,使用完毕后关闭。
SqlSession 类中有超过 20 个方法,我们常用的几乎都是执行语法相关的方法。
这些方法被用来执行定义在 SQL 映射的 XML 文件中的 SELECT、INSERT、UPDATE 和 DELETE 语句。它们都会自行解释,每一句都使用语句的 ID 属性和参数对象,参数可以是原生类型(自动装箱或包装类)、JavaBean、POJO 或 Map。
<T> T selectOne(String statement, Object parameter)
<E> List<E> selectList(String statement, Object parameter)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey)
int insert(String statement, Object parameter)
int update(String statement, Object parameter)
int delete(String statement, Object parameter)
3.5 Mybatis架构
- Mybatis.xml文件是mybatis框架的全局配置文件,配置了mybatis框架运行的环境等信息。Mapper1.xml…是SQL的映射文件,文件中配置了所有的操作数据库的sql语句,这些文件需要在全局配置文件中加载。
- 通过mybatis环境等配置信息构建SqlSessionFactroy ,相当于是产生连接池
- 由会话工厂创建SqlSession即会话(连接),操作数据库需要通过SqlSession进行的。
- Mybatis底层自定义了Executor执行器的接口操作数据库,Executor接口有两个实现,一个基本的执行器,一个是缓存的执行器。
- Mapped statement 也是mybatis框架一个底层的封装对象,他包装了mybatis配置信息以及sql映射信息。Mapper.xml文件中的一个SQL语句对应一个Mapped statement对象,sql的id就是Mapped statement的id。
- Mapped statement对SQL执行输入参数的定义,输入参数包括HashMap、基本类型、pojo,Executor通过Mapped statemen在执行SQL语句 前将输入java对象映射到sql语句中,执行完毕SQL之后,输出映射就是JDBC编码中的对preparedStatement 执行结果的定义。
4、配置日志文件
4.1 添加jar依赖
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
4.2 添加日志配置文件
在resource下添加log4j.properties配置文件
log4j.rootLogger=DEBUG,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
4.3 在mybatis配置文件中添加日志的配置
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
4.4 结果
5、使用原有的Dao方式开发
5.1 创建工具类
5.1.1 ThreadLocal
ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本, 是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。
示例:
class Test{
private ThreadLocal<String> str = new ThreadLocal<String>();
private List<String> list = new ArrayList<String>();
class A extends Thread {
public void run() {
str.set("dengzeyang");
System.out.println("A...." + str.get());
list.add("AAA");
System.out.println("A<<<"+list.get(0));
}
}
class B extends Thread {
public void run() {
System.out.println("B...." + str.get());
list.add("BBB");
System.out.println("B<<<"+list.get(0));
}
}
}
测试代码:
Test2 t=new Test2();
Test2.A a=t.new A();
Test2.B b=t.new B();
a.start();
b.start();
5.1.2 工具类
public class MybatisUtil {
private static ThreadLocal<SqlSession> sqlSessionThreadLocal=new ThreadLocal<>();
private static SqlSessionFactory factory;
static {
Reader reader = null;
try {
reader = Resources.getResourceAsReader("mybatis.xml");
} catch (IOException e) {
e.printStackTrace();
}
factory=new SqlSessionFactoryBuilder().build(reader);
}
public static SqlSession getSqlSession(){
SqlSession sqlSession = sqlSessionThreadLocal.get();
if(sqlSession==null) {
sqlSession = factory.openSession();
sqlSessionThreadLocal.set(sqlSession);
}
return sqlSession;
}
public static void closeSqlSession(){
SqlSession sqlSession = sqlSessionThreadLocal.get();
if(sqlSession!=null){
sqlSession.close();
sqlSessionThreadLocal.remove();
}
}
}
5.2 创建TeamDao接口和实现类
public interface TeamDao {
List<Team> queryAll();
Team queryById(Integer teamId);
int add(Team team);
int update(Team team);
int del(Integer teamId);
}
public class TeamDaoImpl implements TeamDao{
@Override
public List<Team> queryAll() {
SqlSession sqlSession= MybatisUtil.getSqlSession();
return sqlSession.selectList("com.kkb.pojo.Team.queryAll");
}
@Override
public Team queryById(Integer teamId) {
SqlSession sqlSession= MybatisUtil.getSqlSession();
return sqlSession.selectOne("com.kkb.pojo.Team.queryById",teamId);
}
@Override
public int add(Team team) {
SqlSession sqlSession= MybatisUtil.getSqlSession();
int num= sqlSession.insert("com.kkb.pojo.Team.add",team);
sqlSession.commit();
return num;
}
@Override
public int update(Team team) {
SqlSession sqlSession= MybatisUtil.getSqlSession();
int num= sqlSession.update("com.kkb.pojo.Team.update",team);
sqlSession.commit();
return num;
}
@Override
public int del(Integer teamId) {
SqlSession sqlSession= MybatisUtil.getSqlSession();
int num= sqlSession.delete("com.kkb.pojo.Team.del",teamId);
sqlSession.commit();
return num;
}
}
测试:
public class TestTeamDao {
private TeamDao teamDao=new TeamDaoImpl();
@Test
public void testDel(){
int num = teamDao.del(1114);
System.out.println(num);
}
@Test
public void testUpdate(){
Team team = teamDao.queryById(1114);
team.setTeamName("lina");
team.setLocation("bj");
int num = teamDao.update(team);
System.out.println(num);
}
@Test
public void testAdd(){
Team team=new Team();
team.setTeamName("lina的球队");
team.setLocation("北京");
team.setCreateTime(new Date());
int num = teamDao.add( team);
System.out.println(num);
}
@Test
public void test02(){
Team team = teamDao.queryById(1001);
System.out.println(team);
}
@Test
public void test01(){
List<Team> teams = teamDao.queryAll();
teams.forEach(team -> System.out.println(team));
}
}
6、使用Mapper的接口编写Mybatis项目
6.1 什么是Mapper接口
在前面例子中自定义 Dao 接口实现类时发现一个问题:Dao 的实现类其实并没有干什么实质性的工作,它仅仅就是通过 SqlSession 的相关API 定位到映射文件 mapper 中相应 id 的 SQL 语句,真正对 DB 进行操作的工作其实是由框架通过 mapper 中的 SQL 完成的。
所以,MyBatis 框架就抛开了 Dao 的实现类,直接定位到映射文件 mapper 中的相应 SQL 语句,对DB 进行操作。这种对 Dao 的实现方式称为Mapper接口的动态代理方式。
Mapper 动态代理方式无需程序员实现 Dao 接口。接口是由 MyBatis 结合映射文件自动生成的动态代理实现的。
6.2 实现步骤
6.2.1 编写接口TeamMapper.java
public interface TeamMapper {
List<Team> queryAll();
Team queryById(Integer teamId);
int add(Team team);
int update(Team team);
int del(Integer teamId);
}
创建TeamMapper.xml文件,与Team.xml内容几乎一样,只有namespace="com.kkb.mapper.TeamMapper"修改为接口的完全限定名
<?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.kkb.mapper.TeamMapper">
</mapper>
在mybatis.xml配置文件中注册映射文件
<mappers>
<mapper resource="com/kkb/mapper/TeamMapper.xml"/>
</mappers>
6.2.2 getMapper方法获取代理对象
只需调用 SqlSession 的 getMapper()方法,即可获取指定接口的实现类对象。
public class TeamMapperTest {
private SqlSession sqlSession=MybatisUtil.getSqlSession();
@Test
public void test01(){
TeamMapper teamDao= sqlSession.getMapper(TeamMapper.class);
Team team=new Team();
team.setTeamName("勇士");
team.setLocation("金州");
team.setCreateTime(new Date());
int num=teamDao.add(team);
sqlSession.commit();
System.out.println(num);
Team team1 = teamDao.queryById(1001);
team1.setTeamName("lina的球队");
num = teamDao.update(team1);
sqlSession.commit();
System.out.println(num);
num=teamDao.del(1001);
sqlSession.commit();
System.out.println(num);
List<Team> teams = teamDao.queryAll();
teams.forEach(t-> System.out.println(t));
}
}
6.3 实现原理
7、增删改查中的细节
7.1 插入数据的时候获取自增的id
7.1.1 案例准备
7.1.1.1 添加一张新表
CREATE TABLE `gamerecord` (
`recordId` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`homeTeamId` int DEFAULT NULL COMMENT '主队ID',
`gameDate` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '比赛日期',
`score` int DEFAULT NULL COMMENT '得分',
`visitingTeamId` int DEFAULT NULL COMMENT '客队ID',
PRIMARY KEY (`recordId`),
KEY `homeTeamId` (`homeTeamId`),
KEY `visitingTeamId` (`visitingTeamId`),
CONSTRAINT `gamerecord_ibfk_1` FOREIGN KEY (`homeTeamId`) REFERENCES `team` (`teamId`),
CONSTRAINT `gamerecord_ibfk_2` FOREIGN KEY (`visitingTeamId`) REFERENCES `team` (`teamId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
7.1.1.2 实体类
public class GameRecord {
private String recordId;
private Integer homeTeamId;
private Date gameDate;
private Integer score;
private Integer visitingTeamId;
}
7.1.1.3 mapper接口
public interface GameRecordMapper {
int add(GameRecord record);
}
7.1.2 修改配置文件
添加GameRecordMapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kkb.mapper.GameRecordMapper">
<insert id="add" parameterType="com.kkb.pojo.GameRecord">
<selectKey keyProperty="recordId" order="BEFORE" resultType="java.lang.String">
select UUID()
</selectKey>
INSERT INTO `mybatis`.`gamerecord` (`recordId`, `homeTeamId`, `score`, `visitingTeamId`)
VALUES (#{recordId}, #{homeTeamId}, #{score}, #{visitingTeamId})
</insert>
</mapper>
修改TeamMapper.xml文件中的insert节点
<insert id="add" parameterType="com.kkb.pojo.Team" >
<selectKey keyProperty="teamId" order="AFTER" resultType="java.lang.Integer">
select LAST_INSERT_ID()
</selectKey>
INSERT INTO `team` (`teamName`, `location`, `createTime`)
VALUES (#{teamName}, #{location}, #{createTime})
</insert>
7.1.3 测试类
public class TestGameRecordMapper {
private SqlSession sqlSession=MybatisUtil.getSqlSession();
@Test
public void testAdd(){
GameRecordMapper mapper = MybatisUtil.getSqlSession().getMapper(GameRecordMapper.class);
GameRecord record=new GameRecord();
record.setHomeTeamId(1003);
record.setVisitingTeamId(1001);
record.setScore(90);
int add = mapper.add(record);
MybatisUtil.getSqlSession().commit();
System.out.println(add);
}
}
7.2 输入映射
7.2.1 parameterType
parameterType:接口中方法参数的类型,类型必须是完全限定名或别名(稍后讲别名)。该属性非必须,因为Mybatis框架能自行判断具体传入语句的参数,默认值为未设置(unset)。
TeamMapper.xml配置文件中添加如下:
<select id="queryByRange1" resultType="com.kkb.pojo.Team">
select * from team
where teamId>=#{param1} and teamId<=#{param2}
</select>
测试类添加方法:
public class TestTeamMapperArg {
private TeamMapper teamMapper= MybatisUtil.getSqlSession().getMapper(TeamMapper.class);
@Test
public void test01(){
List<Team> teams = teamMapper.queryByRange1(1004, 1010);
teams.forEach(team -> System.out.println(team));
}
}
7.2.3.2 通过@Param注解
在方法的形参前面加入@Param(“自定义参数名称”),mapper文件中使用#{自定义参数名称}的方式传参。
TeamMapper接口添加如下内容:
List<Team> queryByRange2(@Param("min") Integer min, @Param("max") Integer max);
TeamMapper.xml配置文件中添加如下:
<select id="queryByRange2" resultType="com.kkb.pojo.Team">
select * from team
where teamId>=#{min} and teamId<=#{max}
</select>
测试类添加方法:
@Test
public void test02(){
List<Team> teams = teamMapper.queryByRange2(1005, 1011);
teams.forEach(team -> System.out.println(team));
}
7.2.3.3 通过map来传递多个参数
Map 集合可以存储多个值,使用Map向 mapper 文件一次传入多个参数。Map 集合使用 String的 key,Object 类型的值存储参数。 mapper 文件使用 # { key } 引用参数值TeamMapper接口添加如下内容:
List<Team> queryByRange3(Map<String,Object> map);
TeamMapper.xml配置文件中添加如下:
<select id="queryByRange3" resultType="com.kkb.pojo.Team">
select * from team
where teamId>=#{min} and teamId<=#{max}
</select>
测试类添加方法:
@Test
public void test03(){
Map<String, Object> map=new HashMap<>();
map.put("min",1010);
map.put("max",1015);
List<Team> teams = teamMapper.queryByRange3(map);
teams.forEach(team -> System.out.println(team));
}
7.2.3.4 通过pojo类传递多个参数
与map传递多个参数类似,要求映射文件中的参数占位符必须和pojo类中的属性完全一致。
实体类:
public class QueryVO {
private String name;
private Integer min;
private Integer max;
private String location;
}
TeamMapper接口添加如下内容:
List<Team> queryByCondition(QueryVO vo);
TeamMapper.xml配置文件中添加如下:
<select id="queryByCondition" resultType="com.kkb.pojo.Team">
select * from team
where teamId>=#{min} and teamId<=#{max}
and teamName like #{name} and location=#{location}
</select>
测试类添加方法:
@Test
public void test04(){
QueryVO vo=new QueryVO();
vo.setLocation("洛杉矶");
vo.setName("%球队%");
vo.setMin(1001);
vo.setMax(1111);
List<Team> teams = teamMapper.queryByCondition(vo);
teams.forEach(team -> System.out.println(team));
}
7.3 #{} 和 ${}的区别–面试中喜欢出的考题
7.3.1 #{}
#{}:表示一个占位符,通知Mybatis 使用实际的参数值代替。并使用 PrepareStatement 对象执行 sql 语句, #{…}代替sql 语句的“?”。这个是Mybatis 中的首选做法,安全迅速。
<select id="queryById" parameterType="java.lang.Integer" resultType="com.kkb.pojo.Team">
select * from team where teamId=#{id}
</select>
7.3.2 ${}
表示字符串原样替换,通知Mybatis 使用美元符号包含的“字符串”替换所在位置。使用 Statement或者PreparedStatement 把 sql 语句和${}的内容连接起来。一般用在替换表名,列名,不同列排序等操作。
例如:根据球队名称,球队位置查询球队列表
方式1:
TeamMapper接口添加如下内容:
List<Team> queryByName(String teamName);
List<Team> queryByLocation(String location);
TeamMapper.xml配置文件中添加如下:
<select id="queryByName" resultType="team">
select * from team where teamName=#{teamName}
</select>
<select id="queryByLocation" resultType="com.kkb.pojo.Team">
select * from team where location=#{location}
</select>
测试类添加方法:
@Test
public void test05(){
System.out.println("根据球队名称查询:");
List<Team> teams = teamMapper.queryByName("zeyang的球队");
teams.forEach(team -> System.out.println(team));
System.out.println("根据球队位置查询:");
List<Team> teams2 = teamMapper.queryByLocation("洛杉矶");
teams2.forEach(team -> System.out.println(team));
}
方式2:使用不同列作为查询条件
TeamMapper接口添加如下内容:
List<Team> queryByFiled(@Param("column") String column,@Param("columnValue") String columnValue);
TeamMapper.xml配置文件中添加如下:
<select id="queryByFiled" resultType="com.kkb.pojo.Team">
select * from team where ${column}=#{columnValue}
</select>
测试类添加方法:
@Test
public void test06(){
System.out.println("根据球队名称查询:");
List<Team> teams = teamMapper.queryByFiled("teamName","lina的球队");
teams.forEach(team -> System.out.println(team));
System.out.println("根据球队位置查询:");
List<Team> teams2 = teamMapper.queryByFiled("location","洛杉矶");
teams2.forEach(team -> System.out.println(team));
}
7.4 输出映射
7.4.1 resultType
resultType: 执行 sql 得到 ResultSet 转换的类型,使用类型的完全限定名或别名。如果返回的是集合,设置的是集合元素的类型,而不是集合本身。resultType 和 resultMap,不能同时使用。
7.4.1.1 输出简单类型
案例:返回球队的总记录数
TeamMapper接口添加如下内容:
int getCount();
TeamMapper.xml配置文件中添加如下:
<select id="getCount" resultType="int">
select count(teamId)from team
</select>
测试类添加方法:
@Test
public void test07(){
int count = teamMapper.getCount();
System.out.println("总共的行数:"+count);
}
7.4.1.2 输出pojo类型
案例:参考之前的查询所有球队信息
List<Team> queryAll();
<select id="queryAll" resultType="com.kkb.pojo.Team">
select * from team
</select>
7.4.1.3 输出Map类型
当我们只需要查询表中几列数据的时候可以将sql的查询结果作为Map的key和value。一般使用的是Map<Object,Object>。
Map 作为接口返回值,sql 语句的查询结果最多只能有一条记录。大于一条记录会抛出TooManyResultsException异常。
如果有多行,使用List<Map<Object,Object>>。
案例:根据id查询球队名称和位置。
TeamMapper接口添加如下内容:
Map<Object,Object> queryTwoColumn(int teamId);
List<Map<Object,Object>> queryTwoColumnList();
TeamMapper.xml配置文件中添加如下:
<select id="getTwoColumn" resultType="hashmap">
select count(teamId) as 'sum',max(teamId) as 'max' from team
</select>
<select id="getTwoColumnList" resultType="java.util.HashMap">
select count(teamId) as 'sum',max(teamId) as 'max' from team group by location;
</select>
测试类添加方法:
@Test
public void test08(){
Map<String, Object> map = teamMapper.getTwoColumn();
System.out.println(map);
}
@Test
public void test09(){
List<Map<String, Object>> list = teamMapper.getTwoColumnList();
for (Map<String, Object> map : list) {
System.out.println(map);
}
}
7.4.2 resultMap
resultMap 可以自定义 sql 的结果和 java 对象属性的映射关系。更灵活的把列值赋值给指定属性。 常用在列名和 java 对象属性名不一样的情况。 使用方式:
- 先定义 resultMap,指定列名和属性的对应关系。
- 在TeamMapper接口中添加:
List<Team> queryAll2();
TeamMapper.xml映射文件添加:
<select id="queryAll2" resultMap="baseResultMap">
select * from team;
</select>
<resultMap id="baseResultMap" type="com.kkb.pojo.Team">
<id column="teamId" property="teamId" javaType="java.lang.Integer" ></id>
<result column="teamName" property="teamName" javaType="java.lang.String"></result>
<result column="location" property="location" javaType="java.lang.String"></result>
<result column="createTime" property="createTime" javaType="java.util.Date"></result>
</resultMap>
测试:
@Test
public void test10(){
List<Team> teams = teamMapper.queryAll2();
teams.forEach(team-> System.out.println(team));
}
7.4.3 数据库表中列与实体类属性不一致的处理方式
案例准备工作:创建表:
use mybatis;
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`user_id` int NOT NULL AUTO_INCREMENT COMMENT '用户id',
`user_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户姓名',
`user_age` int NULL DEFAULT NULL COMMENT '用户年龄',
PRIMARY KEY (`user_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
INSERT INTO `users` VALUES (1, '贾宝玉', 14);
INSERT INTO `users` VALUES (2, '林黛玉', 13);
INSERT INTO `users` VALUES (3, '薛宝钗', 15);
SET FOREIGN_KEY_CHECKS = 1;
7.4.3.1 使用列别名和resultType
实体类Users.java
public class Users {
private Integer userId;
private String userName;
private Integer userAge;
@Override
public String toString() {
return "Users{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", userAge=" + userAge +
'}';
}
}
接口UsersMapper.java
public interface UsersMapper {
Users queryById(int userId);
}
映射文件UsersMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kkb.mapper.UsersMapper">
<select id="queryById" resultType="com.kkb.pojo.Users">
select user_id as userId,user_name as userName,user_age as userAge from users where user_id=#{id};
</select>
</mapper>
测试类TestUsersMapper.java
public class TestUsersMapper {
private UsersMapper mapper= MybatisUtil.getSqlSession().getMapper(UsersMapper.class);
@Test
public void test1(){
Users user = mapper.queryById(1);
System.out.println(user);
}
}
7.4.3.2 使用resultMap
接口UsersMapper.java添加方法
Users queryByID2(int userId);
映射文件UsersMapper.xml添加如下内容:
<select id="queryById2" resultMap="baseMap">
select * from users where user_id=#{id};
</select>
<resultMap id="baseMap" type="com.kkb.pojo.Users">
<id column="user_id" property="userId"/>
<result column="user_name" property="userName"/>
<result column="user_age" property="userAge"/>
</resultMap>
测试:
@Test
public void test2(){
Users user = mapper.queryById2(1);
System.out.println(user);
}
8、Mybatis的全局配置文件
案例中使用的 mybatis.xml就是Mybatis的全局配置文件。
全局配置文件需要在头部使用约束文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
8.1 配置的内容
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
configuration(配置)
properties--属性:加载外部的配置文件,例如加载数据库的连接信息
Settings--全局配置参数:例如日志配置
typeAliases--类型别名
typeHandlers----类型处理器
objectFactory-----对象工厂
Plugins------插件:例如分页插件
Environments----环境集合属性对象
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
Mappers---映射器:注册映射文件用
8.2 属性(properties)
属性可以在外部进行配置,并可以进行动态替换。我们既可以在 properties 元素的子元素中设置(例如DataSource节点中的properties节点),也可以在 Java 属性文件中配置这些属性。
数据源中有连接数据库的四个参数数据,我们一般都是放在专门的属性文件中,mybatis的全局配置文件直接从属性文件中读取数据即可。
- 在 resources 目录创建 jdbc.properties 文件,文件名称可以自定义。
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
jdbc.username=root
jdbc.password=123456
- mybatis的全局配置文件引入属性文件
<properties resource="jdbc.properties"/>
- 使用属性文件中的值
<dataSource type="POOLED">
<!--创建数据源必备4大参数-->
<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>
8.3 设置 settings
MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为.例如我们配置的日志就是应用之一。其余内容参考设置文档。
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
8.4 类型别名 typeAliases
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
8.4.1 Mybatis中已经支持的别名
下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。
8.4.2 自定义别名
<typeAliases>
<typeAlias type="com.kkb.pojo.Team" alias="Team"></typeAlias>
<package name="com.kkb.pojo"/>
<package name="com.kkb.mapper"/>
</typeAliases>
8.5 映射器 Mappers
配置有多种方式:
8.5.1、 使用相对于类路径的资源引用
语法:<mapper resource=""/>
使用相对于类路径的资源,从 classpath 路径查找文件
例如:<mapper resource="com/kkb/mapper/TeamMapper.xml" />
8.5.2、使用映射器接口实现类的完全限定类名
语法:<mapper class=""/>
使用的mapper接口的完全限定名
要求:接口和映射文件同包同名
例如<mapper class="com.kkb.mapper.GameRecordMapper"/>
8.5.3、将包内的映射器接口实现全部注册为映射器–推荐
语法:<package name=""/>
指定包下的所有Mapper接口
如:<package name="com.kkb.mapper"/>
注意:此种方法要求 Mapper接口名称和 mapper 映射文件名称相同,且在同一个目录中。
8.6 dataSource标签
Mybatis 中访问数据库支持连接池技术,而且是采用的自己的连接池技术。在 Mybatis 的 mybatis.xml配置文件中,通过来实现 Mybatis 中连接池的配置。MyBatis 在初始化时,根据的 type 属性来创建相应类型的的数据源 DataSource。
Mybatis 的数据源分为三类:
- UNPOOLED: 不使用连接池的数据源
- POOLED:使用连接池的数据源
- JNDI:使用JNDI实现的数据源
前两个数据源都实现javax.sql.DataSource接口
8.7 事务
8.7.1、默认是需要手动提交事务的
Mybatis 框架是对 JDBC 的封装,所以 Mybatis 框架的事务控制方式,本身也是用 JDBC 的Connection对象的 commit(), rollback() .Connection 对象的 setAutoCommit()方法来设置事务提交方式的。自动提交和手工提交
<transactionManager type="JDBC"/>
该标签用于指定 MyBatis所使用的事务管理器。MyBatis 支持两种事务管理器类型:JDBC 与 MANAGED。
JDBC:使用JDBC的事务管理机制,通过Connection对象的 commit()方法提交,通过rollback()方法 回滚。默认情况下,mybatis将自动提交功能关闭了,改为了手动提交,观察日志可以看出,所以我们在程序中都需要自己提交事务或者回滚事务。
MANAGED:由容器来管理事务的整个生命周期(如Spring容器)。
8.7.2、自动提交事务
SqlSessionFactory的openSession方法由重载,可以设置自动提交的方式。 如果sqlSession = SqlSessionFactory.openSession(true);参数设置为true,再次执行增删改的时候就不需要执行session.commit()方法,事务会自动提交。
9、Mybatis中的关系映射
表结构如图(建表的SQL语句在资料包中):
9.1 对一关系的映射
9.1.1 实体类
public class Player {
private Integer playerId;
private String playerName;
private Integer playerNum;
private Integer teamId;
private Team team1;
private Team team2;
private Team team3;
}
9.1.2 mapper接口
public interface PlayerMapper {
Player queryById(int playerId);
Player queryById1(int playerId);
Player queryById2(int playerId);
Player queryById3(int playerId);
List<Player> queryByTeamId(int teamId);
}
9.1.3 对一映射方式1:通过关联对象打点调用属性的方式
要求:两表的连接查询
9.1.4 对一映射方式2:直接引用关联对象的Mapper映射
要求:
- 两表的连接查询
- 关联对象中已经存在被引用的resultMap
9.1.5 对一映射方式3:直接引用关联对象的单独查询的方法
要求:
- 不需要两表的连接查询
- 关联对象中已经存在被引用的查询方法
<?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.kkb.mapper.PlayerMapper">
<select id="queryByTeamId" resultMap="baseResultMap">
select * from player where teamId=#{id}
</select>
<select id="queryById" resultMap="baseResultMap">
select * from player where playerId=#{id}
</select>
<resultMap id="baseResultMap" type="Player">
<id column="playerId" property="playerId"/>
<result column="playerName" property="playerName"/>
<result column="playerNum" property="playerNum"/>
<result column="teamId" property="teamId"/>
</resultMap>
<select id="queryById1" resultMap="joinTeamResult1">
SELECT * FROM `player` p INNER JOIN team t
on t.teamId=p.teamId
where playerid=#{id}
</select>
<resultMap id="joinTeamResult1" type="Player" extends="baseResultMap">
<result column="teamId" property="team1.teamId"></result>
<result column="teamName" property="team1.teamName"></result>
<result column="location" property="team1.location"></result>
<result column="createTime" property="team1.createTime"></result>
</resultMap>
<select id="queryById2" resultMap="joinTeamResult2">
SELECT * FROM `player` p INNER JOIN team t
on t.teamId=p.teamId
where playerid=#{id}
</select>
<resultMap id="joinTeamResult2" type="Player" extends="baseResultMap">
<association property="team2" javaType="Team"
resultMap="com.kkb.mapper.TeamMapper.baseResultMap"/>
</resultMap>
<select id="queryById3" resultMap="joinTeamResult3">
select * from player where playerId=#{id}
</select>
<resultMap id="joinTeamResult3" type="Player" extends="baseResultMap">
<association property="team3" javaType="Team"
select="com.kkb.mapper.TeamMapper.queryById" column="teamId"/>
</resultMap>
</mapper>
9.1.6 测试
public class TestPlayerMapper {
private PlayerMapper playerMapper= MybatisUtil.getSqlSession().getMapper(PlayerMapper.class);
private TeamMapper teamMapper=MybatisUtil.getSqlSession().getMapper(TeamMapper.class);
@Test
public void test1(){
Player player = playerMapper.queryById(1);
System.out.println(player);
}
@Test
public void test2(){
Player player = playerMapper.queryById1(1);
System.out.println(player);
}
@Test
public void test3(){
Player player = playerMapper.queryById2(1);
System.out.println(player);
}
}
9.2 对多关系的映射
修改实体类Team.java:
public class Team implements Serializable {
private Integer teamId;
private String teamName;
private String location;
private Date createTime;
private List<Player> playerList1;
private List<Player> playerList2;
}
TeamMapper.java接口中添加方法:
public interface TeamMapper {
Team queryById1(Integer teamId);
Team queryById2(Integer teamId);
}
PlayerMapper.java接口中添加方法:
public interface PlayerMapper {
List<Player> queryByTeamId(int teamId);
}
9.2.1 方式1:连接查询+引用关联对象的结果映射
9.2.2 方式2:引用关联对象的单独查询的方法
TeamMapper.xml添加
<select id="queryById1" resultMap="joinPlayMap1">
select * from team t inner join player p
on t.teamId=p.teamId where t.teamId=#{id}
</select>
<resultMap id="joinPlayMap1" type="Team" extends="baseResultMap">
<collection property="playerList1" javaType="arraylist" ofType="Player"
resultMap="com.kkb.mapper.PlayerMapper.baseResultMap"/>
</resultMap>
<select id="queryById2" resultMap="joinPlayMap2">
select * from team where teamId=#{id}
</select>
<resultMap id="joinPlayMap2" type="Team" extends="baseResultMap">
<collection property="playerList2" javaType="arraylist" ofType="Player"
select="com.kkb.mapper.PlayerMapper.queryByTeamId" column="teamId"/>
</resultMap>
PlayerMapper.xml添加如下内容:
<select id="queryByTeamId" resultMap="baseResultMap">
select * from player where teamId=#{id}
</select>
9.2.3测试:
public class TestPlayerMapper {
private PlayerMapper playerMapper= MybatisUtil.getSqlSession().getMapper(PlayerMapper.class);
private TeamMapper teamMapper=MybatisUtil.getSqlSession().getMapper(TeamMapper.class);
@Test
public void test1(){
Player player = playerMapper.queryById(1);
System.out.println(player);
}
@Test
public void test4(){
Player player = playerMapper.queryById3(1);
System.out.println(player);
}
@Test
public void test5(){
Team team = teamMapper.queryById1(1025);
System.out.println(team);
List<Player> playerList = team.getPlayerList1();
System.out.println("该球队的球员个数:"+playerList.size());
playerList.forEach(player -> System.out.println(player));
}
}
10、动态SQL
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。
咱们之前学习过 JSTL,所以动态 SQL 元素会让你感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
10.1 where标签在select中的使用
案例:球队的多条件查询
if(vo.getName()!=null && !"".equals(vo.getName().trim())){
sql+=" and teamName like '%"+vo.getName().trim()+"%'";
}
if(vo.getBeginTime()!=null ){
sql+=" and getEndTime>"+vo.getBeginTime();
}
if(vo.getBeginTime()!=null ){
sql+=" and createTime<="+vo.getEndTime();
}
if(vo.getLocation()!=null && !"".equals(vo.getLocation().trim())){
sql+=" and location ="+vo.getLocation().trim();
}
自己封装的查询条件类QueryTeamVO.java:
public class QueryTeamVO {
private String name;
private Date beginTime ;
private Date endTime;
private String location;
}
TeamMapper.java接口添加:
List<Team> queryByVO(QueryTeamVO vo);
TeamMapper.xml映射文件添加:
<select id="queryByVO" parameterType="QueryVO" resultMap="baseResultMap" useCache="false">
select * from team
<where>
<if test="name!=null ">
and teamName like concat(concat('%',#{name}),'%')
</if>
<if test="beginTime!=null ">
and createTime>=#{beginTime}
</if>
<if test="endTime!=null ">
and createTime<=#{endTime}
</if>
<if test="location!=null ">
and location=#{location}
</if>
</where>
</select>
测试:
public class TestSQL {
private TeamMapper teamMapper= MybatisUtil.getSqlSession().getMapper(TeamMapper.class);
@Test
public void test1(){
QueryTeamVO vo=new QueryTeamVO();
vo.setName("人");
vo.setEndTime(new Date());
vo.setLocation("加利福尼亚州洛杉矶");
List<Team> teams = teamMapper.queryByVO(vo);
for (Team team : teams) {
System.out.println(team);
}
}
}
10.2 set标签在update中的使用
10.2.1 更新的原有写法
TeamMapper.java接口中的方法:
int update(Team team);
TeamMapper.xml映射文件对应的内容:
<update id="update" parameterType="com.kkb.pojo.Team">
update team set teamName=#{teamName},location=#{location},createTime=#{createTime}
where teamId=#{teamId}
</update>
测试类中添加测试方法:
@Test
public void test2(){
Team team=new Team();
team.setTeamId(1055);
team.setTeamName("zeyang");
int update = teamMapper.update1(team);
MybatisUtil.getSqlSession().commit();
System.out.println(update);
}
10.2.2 使用set标签构建动态的SQL语句
TeamMapper.java接口中添加方法:
int update1(Team team);
TeamMapper.xml映射文件对应的内容:
<update id="update1" parameterType="com.kkb.pojo.Team">
update team
<set>
<if test="teamName!=null">
teamName=#{teamName},
</if>
<if test="location!=null">
location=#{location},
</if>
<if test="createTime!=null">
createTime=#{createTime},
</if>
</set>
where teamId=#{teamId}
</update>
测试类:
@Test
public void test2(){
Team team=new Team();
team.setTeamId(1055);
team.setTeamName("zeyang");
int update = teamMapper.update1(team);
MybatisUtil.getSqlSession().commit();
System.out.println(update);
}
10.3 forEach标签
10.3.1 批量添加
TeamMapper.java接口中添加方法:
void addList(List<Team> list);
TeamMapper.xml映射文件对应的内容:
<insert id="addList" parameterType="arraylist">
INSERT INTO team (teamName,location) VALUES
<foreach collection="list" item="t" separator=",">
(#{t.teamName},#{t.location})
</foreach>
</insert>
测试类:
@Test
public void test3(){
List<Team> list=new ArrayList<>();
for(int i=1;i<=3;i++){
Team team=new Team();
team.setTeamName("zeyang"+i);
team.setLocation("bj"+i);
list.add(team);
}
teamMapper.addList(list);
MybatisUtil.getSqlSession().commit();
}
10.3.2 批量删除
TeamMapper.java接口中添加方法:
void delList(List<Integer> list);
TeamMapper.xml映射文件对应的内容:
<delete id="delList" >
delete from team where teamId in
<foreach collection="list" item="teamId" separator="," open="(" close=")">
#{teamId}
</foreach>
</delete>
测试类:
@Test
public void test4() {
List<Integer> list = new ArrayList<>();
list.add(1109);
list.add(1110);
list.add(1111);
teamMapper.delList(list);
MybatisUtil.getSqlSession().commit();
}
11 、分页插件
11.1 jar依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.10</version>
</dependency>
11.2 在Mybatis全局配置文件中添加插件配置
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
</plugin>
</plugins>
11.3 使用插件
@Test
public void test5() {
PageHelper.startPage(2,5);
List<Team> teams = teamMapper.queryAll();
teams.forEach(team-> System.out.println(team));
PageInfo<Team> info=new PageInfo<>(teams);
System.out.println("分页信息如下:");
System.out.println("当前页:"+info.getPageNum());
System.out.println("总页数:"+info.getPages());
System.out.println("前一页:"+info.getPrePage());
System.out.println("后一页:"+info.getNextPage());
System.out.println("navigatepageNums:"+info.getNavigatepageNums());
for (int num : info.getNavigatepageNums()) {
System.out.println(num);
}
}
PageInfo.java的部分源码:
public class PageInfo<T> extends PageSerializable<T> {
private int pageNum;
private int pageSize;
private int size;
private int startRow;
private int endRow;
private int pages;
private int prePage;
private int nextPage;
private boolean isFirstPage;
private boolean isLastPage;
private boolean hasPreviousPage;
private boolean hasNextPage;
private int navigatePages;
private int[] navigatepageNums;
private int navigateFirstPage;
private int navigateLastPage;
}
12、Mybatis缓存
12.1 缓存作用
缓存是一般的ORM 框架都会提供的功能,目的就是提升查询的效率和减少数据库的压力。将经常查询的数据存在缓存(内存)中,用户查询该数据的时候不需要从磁盘(关系型数据库文件)上查询,而是直接从缓存中查询,提高查询效率,解决高并发问题。
MyBatis 也有一级缓存和二级缓存,并且预留了集成第三方缓存的接口。
Mybatis的缓存结构体系:
12.2 一级缓存:自动开启,SqlSession级别的缓存
在操作数据库时需要构造 sqlSession对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。
当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。
Mybatis默认开启一级缓存,存在内存中(本地缓存)不能被关闭,可以调用clearCache()来清空本地缓存,或者改变缓存的作用域。
12.2.1 一级缓存分析
工作原理图:
当用户发起第一次查询team=1001的时候,先去缓存中查找是否有team=1001的对象;如果没有,继续向数据中发送查询语句,查询成功之后会将teamId=1001的结果存入缓存中;
当用户发起第2次查询team=1001的时候,先去缓存中查找是否有team=1001的对象,因为第一次查询成功之后已经存储到缓存中,此时可以直接从缓存中获取到该数据,意味着不需要再去向数据库发送查询语句。
如果SqlSession执行了commit(有增删改的操作),此时该SqlSession对应的缓存区域被整个清空,目的避免脏读。
前提:SqlSession未关闭。
测试类:
public class TestCache {
private SqlSession sqlSession= MybatisUtil.getSqlSession();
@Test
public void test1() {
Team t1=sqlSession.selectOne("com.kkb.mapper.TeamMapper.queryById",1001);
System.out.println(t1);
Team t2=sqlSession.selectOne("com.kkb.mapper.TeamMapper.queryById",1001);
System.out.println(t2);
MybatisUtil.closeSqlSession();
sqlSession=MybatisUtil.getSqlSession();
Team t3=sqlSession.selectOne("com.kkb.mapper.TeamMapper.queryById",1001);
System.out.println(t3);
int num=sqlSession.delete("com.kkb.mapper.TeamMapper.del",10000);
sqlSession.commit();
System.out.println("删除结果:"+num);
Team t4=sqlSession.selectOne("com.kkb.mapper.TeamMapper.queryById",1001);
System.out.println(t4);
sqlSession.close();
}
}
12.2.2 清空缓存的方式
- session.clearCache( ) ;
- execute update(增删改) ;
- session.close( );
- xml配置 flushCache=“true” ;
- rollback;
- commit。
12.3 二级缓存:Mapper级别的缓存
多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession去操作数据库得到数据会存在二级缓存区域,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace。
不同的sqlSession两次执行相同namespace下的sql语句参数相同即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。
Mybatis默认没有开启二级缓存,需要在setting全局参数中配置开启二级缓存。
如果缓存中有数据就不用从数据库中获取,大大提高系统性能。
二级缓存原理图:
12.3.1 使用二级缓存步骤
二级缓存是mapper范围级别的,默认不启用。
- 在Mybatis框架的全局配置文件中开启二级缓存
<setting name="cacheEnabled" value="true"/>
- 在需要二级缓存的Mapper中添加缓存标志
- 实体类必须实现Serializable接口
- 测试二级缓存
如果两个session不是从同一个Factory获取,那么二级缓存将不起作用。
@Test
public void test2() {
SqlSession sqlSession1 = MybatisUtil.getSqlSession();
Team t1 = sqlSession1.selectOne("com.kkb.mapper.TeamMapper.queryById", 1001);
System.out.println(t1);
MybatisUtil.closeSqlSession();
SqlSession sqlSession2 = MybatisUtil.getSqlSession();
Team t2 = sqlSession2.selectOne("com.kkb.mapper.TeamMapper.queryById", 1001);
System.out.println(t2);
MybatisUtil.closeSqlSession();
SqlSession sqlSession3 = MybatisUtil.getSqlSession();
int num = sqlSession3.delete("com.kkb.mapper.TeamMapper.del", 10000);
System.out.println("删除的结果:" + num);
sqlSession3.commit();
MybatisUtil.closeSqlSession();
SqlSession sqlSession4 = MybatisUtil.getSqlSession();
Team t3 = sqlSession4.selectOne("com.kkb.mapper.TeamMapper.queryById", 1001);
System.out.println(t3);
MybatisUtil.closeSqlSession();
}
12.3.2 二级缓存的禁用
对于变化比较频繁的SQL,可以禁用二级缓存。
在开始了二级缓存的XML中对应的statement中设置useCache=false禁用当前Select语句的二级缓存,意味着该SQL语句每次只需都去查询数据库,不会查询缓存。
useCache默认值是true。对于一些很重要的数据尽不放在二级缓存中。
12.3.3 缓存的属性配置
<cache>
<property name="eviction" value="LRU"/>
<property name="flushInterval" value="60000"/>
<property name="size" value="1024"/>
<property name="readOnly" value="true"/>
</cache>
源码: 如果想在命名空间中共享相同的缓存配置和实例,可以使用cache-ref 元素来引用另外一个缓存。
<cache-ref namespace="com.kkb.mapper.TeamMapper" />
//引用TeamMapper命名空间中的cache。
13、反向生成插件
13.1 插件的配置
在pom.xml文件中的中中添加如下插件配置
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.5</version>
<configuration>
<configurationFile>src/main/resources/generatorConfig.xml</configurationFile>
<overwrite>true</overwrite>
</configuration>
<dependencies>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.5</version>
</dependency>
</dependencies>
</plugin>
generatorConfig.xml内容
<?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>
<classPathEntry
location="D:\repository\mysql\mysql-connector-java\8.0.23\mysql-connector-java-8.0.23.jar" />
<context id="MyBatis" targetRuntime="MyBatis3">
<commentGenerator>
<property name="suppressAllComments" value="true" />
</commentGenerator>
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT"
userId="root"
password="123456">
</jdbcConnection>
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<javaModelGenerator targetPackage="org.xzk.pojo"
targetProject="src\main\java">
<property name="trimStrings" value="true" />
</javaModelGenerator>
<sqlMapGenerator targetPackage="org.xzk.mapper"
targetProject="src\main\resources">
</sqlMapGenerator>
<javaClientGenerator type="XMLMAPPER"
targetPackage="org.xzk.mapper"
targetProject="src\main\java">
</javaClientGenerator>
<table tableName="Team">
<property name="useActualColumnNames" value="true"/>
</table>
<table tableName="Player">
<property name="useActualColumnNames" value="true"/>
</table>
<table tableName="gameRecord">
<property name="useActualColumnNames" value="true"/>
</table>
</context>
</generatorConfiguration>
注意只能运行一次,运行完毕显示BUILD SUCCESS即为成功。
13.2 使用反向生成中的多条件查询方法
public class TestGenerator {
private TeamMapper mapper= MybatisUtil.getSqlSession().getMapper(TeamMapper.class);
@Test
public void test1(){
Team team = mapper.selectByPrimaryKey(1001);
System.out.println(team);
}
@Test
public void test2(){
Team team=new Team();
team.setTeamName("lina-test");
team.setLocation("bj");
int i = mapper.insert(team);
MybatisUtil.getSqlSession().commit();
System.out.println(i);
}
@Test
public void test3(){
TeamExample example=new TeamExample();
TeamExample.Criteria criteria = example.createCriteria();
criteria.andTeamNameLike("人");
criteria.andTeamIdBetween(1001,1100);
example.setOrderByClause("teamName desc");
List<Team> teams = mapper.selectByExample(example);
for (Team team : teams) {
System.out.println(team);
}
}
}
|