java-spring

mybaits实现对数据库的整合操作

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编程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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");
//SQL语句
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解决的问题

1、数据库连接的创建、释放连接的频繁操作造成资源的浪费从而影响系统的性能。

2、SQL语句编写在代码中,硬编码(代码写在项目代码中)造成代码不容易维护,实际应用中SQL语句变化的可能性比较大,一旦变动就需要改变java类。

3、使用preparedStatement的时候传递参数使用占位符,也存在硬编码,因为SQL语句变化,必须修改源码。

4、对结果集的解析中也存在硬编码。

2. Mybatis入门案例

2.1 创建数据库和表

1
2
3
4
5
6
7
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依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<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网址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?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>
<!--配置 mybatis 环境-->
<environments default="development">
<!--id:数据源的名称-->
<environment id="development">
<!--事务类型:使用 JDBC 事务,使用 Connection 的提交和回滚-->
<transactionManager type="JDBC"></transactionManager>
<!--数据源 dataSource:创建数据库 Connection 对象
type: POOLED 使用数据库的连接池
-->
<dataSource type="POOLED">
<!--连接数据库的四大参数
注意数据库版本使用的是MySQL8,如果是mysql5的话,driver和url都不一样,参考学过的JDBC-->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?
useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false&amp;serverTimezone=GMT"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
</configuration>

2.4 编写实体类

实体类中的属性必须与表中的列名保持一致

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.kkb.pojo;
import java.util.Date;
/**
* ClassName: Team
* 球队的实体类
* @author wanglina
* @version 1.0
*/
public class Team {
private Integer teamId;
private String teamName;
private String location;
private Date createTime;
@Override
public String toString() {
return "Team{" +
"teamId=" + teamId +
", teamName='" + teamName + '\'' +
", location='" + location + '\'' +
", createTime=" + createTime +
'}';
}
//省略set get方法
}

2.5 编写ORM映射文件

我们是针对实体类Team.java和表Team进行ORM映射.
Mybatis框架中,ORM映射是针对SQL语句进行,Mybatis框架将SQL语句抽取到了XML中。所以我们需要针对每个实体类编写XML映射文件。

  1. XML映射文件必须与实体类在同一个包下面
  2. XML映射文件名称必须是实体类名称一致

头文件在网站复制即可。

映射文件:实体类与数据库表的映射,SQL语句从代码的硬编码中抽取出来了

namespace=”名称必须与映射的类的名字一致,是完全限定名

id=”自定义名称,id不能重复;相当于dao中的方法名称”

resultType=”使用的要求:实体类中的属性名与表中的列名一致”

1
2
3
4
5
6
7
8
9
10
11
12
13
<?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.kkb.pojo.Team">
<!-- id="自定义名称,id不能重复;相当于dao中的方法名称"
resultType="使用的要求:实体类中的属性名与表中的列名一致"
-->
<select id="queryAll" resultType="com.kkb.pojo.Team">
select * from team;
</select>
</mapper>

2.6 将映射文件注册到mybatis的配置xml文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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">
......
</environment>
</environments>
<!-- 注册映射文件 -->
<mappers>
<mapper resource="com/kkb/pojo/Team.xml"/>
</mappers>
</configuration>

2.7 配置映射文件的扫描位置

pom.xml文件配置映射文件的扫描路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<build>
<resources>
<resource>
<directory>src/main/java</directory><!--所在的目录-->
<includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>

</resource>
</resources>
<plugins>
//省略
</plugins>
</build>

2.8 使用Mybatis框架的核心接口测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class TeamTest {
//mybatis的配置文件--相当于创建工厂的图纸
private String resource="mybatis.xml";
@Test
public void testFindAll(){
try {
//1、读取mybatis的配置文件
Reader reader = Resources.getResourceAsReader(resource) ;
//2、创建SqlSessionFactory对象,目的是获取sqlSession--根据图纸创建工厂
SqlSessionFactory sqlSessionFactory= new SqlSessionFactoryBuilder().build(reader);
//3、创建可以执行SQL语句的SqlSession--工厂创建产品
SqlSession sqlSession = sqlSessionFactory.openSession();
//4、执行SQL语句
List<Team> teamList = sqlSession.selectList("com.kkb.pojo.Team.findAll");
//5、循环输出查询的结果
for (Team team : teamList) {
System.out.println(team);
}
//6、关闭SqlSession,释放资源
sqlSession.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

2.9入门案例的增删改查

  1. 根据ID查询单个对象
    Team.xml的映射文件中添加:

    根据ID查询
    parameterType=”参数的类型”,目前只支持一个参数,可以省略,框架可以自动判断
    where teamId=#{id}: #{id}表示参数 id-自定义,只需要符合命名规范即可,没有实际对应意义

    1
    2
    3
    4
    5
    6
    7
    8
    <!--
    根据ID查询
    parameterType="参数的类型",目前只支持一个参数
    where teamId=#{id}: #{id}表示参数 id-自定义,只需要符合命名规范即可,没有实际对应意义
    -->
    <select id="findById" parameterType="int" resultType="com.kkb.pojo.Team">
    select * from team where teamId=#{id}
    </select>

    测试类中添加如下内容:

    @Before注解下的before方法:执行在测试方法前,构建连接,创建sql工厂

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    private SqlSession sqlSession;
    @Test
    public void testFindById(){
    Team team=sqlSession.selectOne("com.kkb.pojo.Team.queryById",1001);
    System.out.println(team);
    }
    @Before//表示该方法在执行测试方法之前先执行
    public void before() throws IOException {
    //1、读取mybatis的配置文件
    Reader reader = Resources.getResourceAsReader(resource) ;
    //2、创建SqlSessionFactory对象,目的是获取sqlSession--根据图纸创建工厂
    SqlSessionFactory sqlSessionFactory= new SqlSessionFactoryBuilder().build(reader);
    //3、创建可以执行SQL语句的SqlSession--工厂创建产品
    sqlSession = sqlSessionFactory.openSession();
    }
    @After
    public void after(){
    sqlSession.close();
    }
  2. 增删改
    Team.xml的映射文件中添加:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <!--删除一个球队 -->
    <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>
    <!--添加一个球队
    parameterType="com.kkb.pojo.Team" 将对象作为参数,
    #{值} 值必须是实体类中的属性名称,其实就是占位符?
    -->
    <insert id="add" parameterType="com.kkb.pojo.Team" >
    INSERT INTO `team` (`teamName`, `location`, `createTime`)
    VALUES (#{teamName}, #{location}, #{createTime})
    </insert>

    测试类中添加如下方法:

    ​ sqlSession.commit();//手动提交事务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    @Test
    public void testDel(){
    int num = sqlSession.delete("com.kkb.pojo.Team.del", 1049);
    sqlSession.commit();
    System.out.println(num);
    }
    @Test
    public void testUpdate(){
    Team team=sqlSession.selectOne("com.kkb.pojo.Team.queryById",1049);
    team.setTeamName("linawang的球队");
    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("lina的球队");
    team.setLocation("北京");
    team.setCreateTime(new Date());
    int num = sqlSession.insert("com.kkb.pojo.Team.add", team);//增删改必须手动提交事务
    sqlSession.commit();//手动提交事务
    System.out.println(num);
    }

3. Mybatis对象分析

  1. Resources
    Resources 类,顾名思义就是资源,用于读取资源文件。其有很多方法通过加载并解析资源文件,返回不同类型的 IO 流对象。

  2. SqlSessionFactoryBuilder
    SqlSessionFactory 的 创 建 , 需 要 使 用 SqlSessionFactoryBuilder 对 象 的 build() 方 法 。 事实上使用SqlSessionFactoryBuilder的原因是将SqlSessionFactory这个复杂对象的创建交由Builder来执行,也就是使用了建造者设计模式。

    建造者模式: 又称生成器模式,是一种对象的创建模式。 可以将一个产品的内部表象与产品的生成过程分割开来, 从而可以使一个建造过程生成具有
    不同的内部表象的产品(将一个复杂对象的构建与它的表示分离, 使得同样的构建过程可以创建不同的表示). 这样用户只需指定需要建造的类型就可
    以得到具体产品,而不需要了解具体的建造过程和细节.
    在建造者模式中,角色分指导者(Director)与建造者(Builder): 用户联系指导者, 指导者指挥建造者, 最后得到产品. 建造者模式可以强制实行
    一种分步骤进行的建造过程。

  3. SqlSessionFactory
    SqlSessionFactory 接口对象是一个重量级对象(系统开销大的对象),是线程安全的,所以一个应用只需要一个该对象即可。创建
    SqlSession 需要使用 SqlSessionFactory 接口的的 openSession()方法。

    默认的 openSession()方法没有参数,它会创建有如下特性的 SqlSession:
    1、会开启一个事务(也就是不自动提交)。
    2、将从由当前环境配置的 DataSource 实例中获取 Connection 对象。事务隔离级别将会使用驱动或数据源的默认设置。
    3、预处理语句不会被复用,也不会批量处理更新。
    openSession(true):创建一个有自动提交功能的 SqlSession
    openSession(false):创建一个非自动提交功能的 SqlSession,需手动提交
    openSession():同 openSession(false)

  4. SqlSession
    SqlSession 接口对象用于执行持久化操作。一个 SqlSession 对应着一次数据库会话,一次会话以SqlSession 对象的创建开始,以
    SqlSession 对象的关闭结束。
    SqlSession 接口对象是线程不安全的,所以每次数据库会话结束前,需要马上调用其 close()方法,将其关闭。再次需要会话,再次创建。
    SqlSession 在方法内部创建,使用完毕后关闭。
    SqlSession 类中有超过 20 个方法,我们常用的几乎都是执行语法相关的方法。
    这些方法被用来执行定义在 SQL 映射的 XML 文件中的 SELECT、INSERT、UPDATE 和 DELETE 语句。它们都会自行解释,每一句都使用语
    句的 ID 属性和参数对象,参数可以是原生类型(自动装箱或包装类)、JavaBean、POJO 或 Map。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <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)
    <!--
    selectOne 和 selectList 的不同仅仅是 selectOne 必须返回一个对象或 null 值。如果返回值多于一个,那么就会抛出异常。
    selectMap 稍微特殊一点,因为它会将返回的对象的其中一个属性作为 key 值,将对象作为 value 值,从而将多结果集转为 Map 类型值。因为
    并不是所有语句都需要参数,所以这些方法都重载成不需要参数的形式。
    -->

4. 日志

4.1 添加jar依赖

1
2
3
4
5
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

4.2 添加日志配置文件

在resource下添加log4j.properties配置文件

DEBUG info warning error:筛选日志的几个等级,从松到严

1
2
3
4
5
6
# Global logging configuration info warning error
log4j.rootLogger=DEBUG,stdout
# Console output...
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配置文件中添加日志的配置

1
2
3
4
5
6
<configuration>
<!--配置日志,注意顺序:查看属性点击configuration进入查看即可-->
<settings>
<setting name="logImpl" value="LOG4J" />
</settings>
......

4.4 结果

image-20211225143150226

5. 使用原有的Dao方式开发

5.1 创建工具类

5.1.1 ThreadLocal

ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为
ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,
是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。
示例:

  1. 分别创建threadlocal和list的对象

    image-20211226130035493

  2. 创建两个

image-20211226125840601

运行结果:
image-20211226125911788

可以看到ThreadLocal保存的内容,在两个线程中是相互独立的副本,没有关联

list保存的内容被线程共享

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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("wanglina");
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 工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.kkb.util;

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 java.io.IOException;
import java.io.Reader;

/**
* ClassName: MybatisUtil
* 工具类:获取连接和关闭连接
* @author wanglina
* @version 1.0
*/
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);//根据图纸创建出了工厂
}

/**
* 获取连接
* @return
*/
public static SqlSession getSqlSession(){
//从ThreadLocal中获取
SqlSession sqlSession = sqlSessionThreadLocal.get();
if(sqlSession==null) {
//创建sqlSession
sqlSession = factory.openSession();
//将sqlSession与线程进行绑定
sqlSessionThreadLocal.set(sqlSession);
}
return sqlSession;
}

/**
* 关闭连接
*/
public static void closeSqlSession(){
//从ThreadLocal中获取
SqlSession sqlSession = sqlSessionThreadLocal.get();
if(sqlSession!=null){
sqlSession.close();
sqlSessionThreadLocal.remove();
}
}
}

5.1.3 创建TeamDao接口和实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
```

# 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 实现步骤

1. 编写接口TeamMapper.java

```java
public interface TeamMapper {
List<Team> queryAll();
Team queryById(Integer teamId);
int add(Team team);
int update(Team team);
int del(Integer teamId);
}
  1. 创建TeamMapper.xml文件, 与Team.xml内容几乎一样,只有namespace=”com.kkb.mapper.TeamMapper”修改为接口的完全限定

  2. 在mybatis.xml配置文件中注册映射文件

    1
    2
    3
    4
    <mappers>
    <!--省略其他-->
    <mapper resource="com/kkb/mapper/TeamMapper.xml"/>
    </mappers>
  3. getMapper方法获取代理对象

    只需调用 SqlSession 的 getMapper()方法,即可获取指定接口的实现类对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    public class TestTeamMapper {
    //通过动态代理的方式产生实现类
    private TeamMapper teamMapper= MybatisUtil.getSqlSession().getMapper(TeamMapper.class);

    @Test
    public void testAdd(){
    Team team=new Team();
    team.setLocation("杭州");
    team.setTeamName("开课吧球队1");
    team.setCreateTime(new Date());
    int add = teamMapper.add(team);
    MybatisUtil.getSqlSession().commit();
    System.out.println(add);
    System.out.println("新增的球队ID:"+team.getTeamId());
    }

    @Test
    public void testQueryByID(){
    TeamMapper teamMapper= MybatisUtil.getSqlSession().getMapper(TeamMapper.class);
    Team team = teamMapper.queryById(1025);
    System.out.println(team);
    }

    @Test
    public void testDel(){
    TeamMapper teamMapper= MybatisUtil.getSqlSession().getMapper(TeamMapper.class);
    int del = teamMapper.del(1045);
    MybatisUtil.getSqlSession().commit();
    System.out.println(del);
    }
    }

7、增删改查中的细节

7.1 插入数据的时候获取自增的id

image-20211226160924332

字符串的自增:

order属性的before:表示selectkey里的语句先于insert语句执行

1
2
3
4
5
6
7
8
9
<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>

7.2 4种传参方式

接口文件:

1
2
3
4
5
6
7
public interface TeamMapper {
//四种传参方式
List<Team> queryByRange1(Integer min,Integer max);
List<Team> queryByRange2(@Param("min") Integer min,@Param("max") Integer max);
List<Team> queryByRange3(Map<String,Object> map);
List<Team> queryByCondition(QueryVO vo);
}

xml文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!--细节1:
mybatis3.3版本之前:可以直接写#{0} #{1}
从mybatis3.4开始:#{arg0} #{arg1}... 或者是 #{param1} #{param2}...
细节2:sql语句中不能使用小于号,使用转移符号替换;大于号没有限制,也可以使用转义符号替换 &gt;
-->
<select id="queryByRange1" resultType="com.kkb.pojo.Team">
select * from team
where teamId>=#{param1} and teamId&lt;=#{param2}
</select>
<!--
方式2:#{}中的名称必须与接口的方法中的参数注解@Param()保持一致-->
<select id="queryByRange2" resultType="com.kkb.pojo.Team">
select * from team
where teamId>=#{min} and teamId&lt;=#{max}
</select>

<!--
方式3:#{}中的名称必须Map集合中的key保持一致-->
<select id="queryByRange3" resultType="com.kkb.pojo.Team">
select * from team
where teamId>=#{min} and teamId&lt;=#{max}
</select>

<!--
方式4:#{}中的名称必须pojo的参数的属性保持一致-->
<select id="queryByCondition" resultType="com.kkb.pojo.Team">
select * from team
where teamId>=#{min} and teamId&lt;=#{max}
and teamName like #{name} and location=#{location}
</select>

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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));
}

@Test
public void test02(){
List<Team> teams = teamMapper.queryByRange2(1005, 1011);
teams.forEach(team -> System.out.println(team));
}

@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));
}
@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 #{} 和 ${}的区别

{}:表示一个占位符,通知Mybatis 使用实际的参数值代替。并使用 PrepareStatement 对象执行 sql 语句, #{…}代替sql 语句的“?”。这个是Mybatis 中的首选做法,安全迅速。

\${}:表示字符串原样替换,通知Mybatis 使用\$包含的“字符串”替换所在位置。使用 Statement或者PreparedStatement 把 sql 语句和\${}的内容连接起来。一般用在替换表名,
列名,不同列排序等操作。

方式1:

TeamMapper接口添加如下内容:

1
2
List<Team> queryByName(String teamName);
List<Team> queryByLocation(String location);

TeamMapper.xml配置文件中添加如下:

1
2
3
4
5
6
<select id="queryByName" resultType="com.kkb.pojo.Team">
select * from team where teamName=#{teamName}
</select>
<select id="queryByLocation" resultType="com.kkb.pojo.Team">
select * from team where location=#{location}
</select>

测试类添加方法:

1
2
3
4
5
6
7
8
9
@Test
public void test05(){
System.out.println("根据球队名称查询:");
List<Team> teams = teamMapper.queryByName("lina的球队");
teams.forEach(team -> System.out.println(team));
System.out.println("根据球队位置查询:");
List<Team> teams2 = teamMapper.queryByLocation("洛杉矶");
teams2.forEach(team -> System.out.println(team));
}

方式2:使用不同列作为查询条件

TeamMapper接口添加如下内容:

1
List<Team> queryByFiled(@Param("column") String column,@Param("columnValue") String columnValue);

TeamMapper.xml配置文件中添加如下:

1
2
3
<select id="queryByFiled" resultType="com.kkb.pojo.Team">
select * from team where ${column}=#{columnValue}
</select>

测试类添加方法

1
2
3
4
5
6
7
8
9
@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));
}

根据xml和测试方法可以明显看出差异:

方式1:两个xml语句,实现不同字段的查询

方式2: 一个xml语句,实现不同字段的查询,\${}可以表示任意字段

7.4 输出映射

7.4.1 resultType

对应调用该条sql语句的方法的返回值类型

  1. 输出简单类型

    案例:返回球队的总记录数
    TeamMapper接口添加如下内容:

    1
    int getCount();

    TeamMapper.xml配置文件中添加如下:

    1
    2
    3
    4
    5
    6
    <!-- 只有返回的结果是单行的时候,返回值类型才可以指定为基本类型
    如果是单行多列,也取不到后面的列的值;
    如果返回多行会报异常:TooManyResultsException-->
    <select id="getCount" resultType="java.lang.Integer">
    select count(teamId) from team
    </select>

    测试方法:

    1
    2
    3
    4
    5
    @Test
    public void test07(){
    int count = teamMapper.getCount();
    System.out.println("总共的行数:"+count);
    }
  2. 输出pojo类型

    参考之前的查询所有球队信息:

    1
    List<Team> queryAll();
    1
    2
    3
    4
    <!--接口方法返回是集合类型,但是映射文件中的resultType需要指定集合中的类型,不是集合本身。-->
    <select id="queryAll" resultType="com.kkb.pojo.Team">
    select * from team;
    </select>
  3. 输出map类型

    当我们只需要查询表中几列数据的时候可以将sql的查询结果作为Map的key和value。一般使用的是Map。Map 作为接口返回值,sql 语句的查询结果最多只能有一条记录。大于一条记录会抛出TooManyResultsException异常。如果有多行,使用List>.

    案例:根据id查询球队名称和位置。

    TeamMapper接口添加如下内容

    1
    2
    Map<Object,Object> queryTwoColumn(int teamId);
    List<Map<Object,Object>> queryTwoColumnList();

    TeamMapper.xml配置文件中添加如下:

    1
    2
    3
    4
    5
    6
    <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>

    测试类添加方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Test
    public void test08(){
    Map<String, Object> map = teamMapper.queryTwoColumn();
    System.out.println(map);
    }
    @Test
    public void test09(){
    List<Map<String, Object>> list = teamMapper.queryTwoColumnList();
    for (Map<String, Object> map : list) {
    System.out.println(map);
    }
    }

7.4.2 resultmap

创建resultMap:相当于自己编写表中的列与实体类中的属性的映射
id:resultMap的名称,要求唯一
type:期待要映射为java的类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--创建resultMap:相当于自己编写表中的列与实体类中的属性的映射
id:resultMap的名称,要求唯一
type:期待要映射为java的类型
-->
<resultMap id="baseResultMap" type="com.kkb.pojo.Team">
<!--一般主键列用id,其余列用result
column:表示数据库表中的列名,不区分大小写
property:表示实体类中的对应的属性名,区分大小写
javaType:实体类中的对应的属性的类型,可以省略,mybatis会自己推断
jdbcType="数据库中的类型column的类型" 一般省略
-->
<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>

9. 关系映射

image-20211226202450600

9.1 对一关系的映射

实体类

1
2
3
4
5
6
7
8
9
10
public class Player {
private Integer playerId;
private String playerName;
private Integer playerNum;
private Integer teamId;
//多对一的体现:多方持有一方的对象 要有get方法
private Team team1;//关联对象--多个球员可以属于同一个球队;
private Team team2;//关联对象--多个球员可以属于同一个球队;
private Team team3;//关联对象--多个球员可以属于同一个球队;
//省略set get 和toString方法

mapper接口

1
2
3
4
5
6
public interface PlayerMapper {
Player queryById(int playerId);
Player queryById1(int playerId);
Player queryById2(int playerId);
Player queryById3(int playerId);
}