Mybatis学习笔记03

Mybatis 中的连接池以及事务控制

  • 连接池就是用于存储连接的一个容器
  • 容器其实就是一个集合对象,该集合必须是线程安全的,不能两个线程拿到同一个连接
  • 该集合还必须实现队列的特性:先进先出

连接池配置的位置

主配置文件 SqlMapConfig.xml 中的 dataSource 标签,type 属性就是表示采用何种连接池方式。

配置的方式(type 属性的取值)

  1. POOLED

    采用传统的 javax.sql.DataSource 规范中的连接池,mybatis 中有针对规范的实现

  2. UNPOOLED

    采用传统的获取连接的方式,虽然也实现 Javax.sql.DataSource 接口,但是并没有使用池的思想。

  3. JNDI

    采用服务器提供的 JNDI 技术实现,来获取 DataSource 对象,不同的服务器所能拿到 DataSource 是不一样的。

    注意:

    此配置如果不是 web 或者 maven 的 war 工程,是不能使用的

    下文中使用的是 tomcat 服务器,采用连接池就是 dbcp 连接池

在这三种数据源中,我们一般采用的是 POOLED 数据源(很多时候我们所说的数据源就是为了更好的管理数据 库连接,也就是我们所说的连接池技术)。

JNDI 数据源的具体使用

JNDI:

Java Naming and Directory Interface,是SUN公司推出的一套规范,属于 JavaEE 的技术之一。目的是模仿 windows 系统中的注册表,在服务器中注册数据源

  1. 创建 Maven 的 web 工程并导入坐标:

    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
    <dependencies>
    <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.4</version>
    </dependency>

    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.19</version>
    </dependency>

    <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
    </dependency>

    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13</version>
    <scope>test</scope>
    </dependency>

    <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    </dependency>

    <dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>javax.servlet.jsp-api</artifactId>
    <version>2.3.3</version>
    </dependency>

    </dependencies>
  2. webapp 文件下创建 META-INF 目录,并在 META-INF 目录中建立一个名为 context.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
    <?xml version="1.0" encoding="UTF-8"?>
    <Context>
    <!--
    <Resource
    name="jdbc/eesy_mybatis" 数据源的名称,可以自己指定
    type="javax.sql.DataSource" 数据源类型,我们要存什么样的对象
    auth="Container" 数据源提供者,这里的容器指的是Tomcat
    maxActive="20" 最大活动数
    maxWait="10000" 最大等待时间
    maxIdle="5" 最大空闲数
    username="root" 用户名
    password="1234" 密码
    driverClassName="com.mysql.jdbc.Driver" 驱动类
    url="jdbc:mysql://localhost:3306/eesy_mybatis" 连接url字符串
    />
    -->
    <Resource
    name="jdbc/learn"
    type="javax.sql.DataSource"
    auth="Container"
    maxActive="20"
    maxWait="10000"
    maxIdle="5"
    username="root"
    password="4869"
    driverClassName="com.mysql.cj.jdbc.Driver"
    url="jdbc:mysql://localhost:3306/learn?serverTimezone=GMT%2B8"
    />
    </Context>
  3. 修改 SqlMapConfig.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
    31
    32
    33
    34
    35
    36
    <configuration>
    <!-- 配置连接数据库的信息 -->
    <properties resource="jdbcConfig.properties"/>
    <!-- 使用 typeAliases 配置别名,它只能配置 com.conv.domain 中类的别名 -->
    <typeAliases>
    <package name="com.conv.domain"/>
    </typeAliases>

    <!--配置环境-->
    <environments default="mysql">
    <!--配置myslq的环境-->
    <environment id="mysql">
    <!--配置事务的类型-->
    <transactionManager type="JDBC"/>
    <!--配置数据源(连接池)-->
    <dataSource type="JNDI">
    <property name="data_source" value="java:comp/env/jdbc/learn"/>
    </dataSource>
    </environment>
    </environments>

    <!--指定映射配置文件的位置,映射配置文件指的是每个DAO独立的配置文件
    如果是用注解来配置的话,此处应该使用class属性指定被注解的dao全限定类名
    -->
    <mappers>
    <!--xml配置,路径是resource下的路径-->
    <!--<mapper resource="com.conv.dao/IUserDao.xml"/>-->

    <!--使用注解的配置-->
    <!--<mapper class="com.conv.dao.IUserDao"/>-->

    <!--package 标签用于指定dao接口所在的包,
    当指定了之后就不需要再写 mapper 以及 class 或 resource 了-->
    <package name="com.conv.dao"/>
    </mappers>
    </configuration>
  4. 修改 index.jsp

    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
    <%@ page import="java.io.InputStream" %>
    <%@ page import="org.apache.ibatis.io.Resources" %>
    <%@ page import="org.apache.ibatis.session.SqlSessionFactoryBuilder" %>
    <%@ page import="org.apache.ibatis.session.SqlSessionFactory" %>
    <%@ page import="org.apache.ibatis.session.SqlSession" %>
    <%@ page import="com.conv.dao.IUserDao" %>
    <%@ page import="com.conv.domain.User" %>
    <%@ page import="java.util.List" %>
    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
    <html>
    <body>
    <h2>Hello World!</h2>
    <%
    //1.读取配置文件
    InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
    //2.根据配置文件构建SqlSessionFactory
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    SqlSessionFactory factory = builder.build(in);
    //3.使用SqlSessionFactory创建SqlSession对象
    SqlSession sqlSession = factory.openSession();
    //4.使用SqlSession构建Dao的代理对象
    IUserDao userDao = sqlSession.getMapper(IUserDao.class);
    //5.执行dao中的findAll方法
    List<User> users = userDao.findAll();
    for(User user : users){
    System.out.println(user);
    }
    //6.释放资源
    sqlSession.close();
    in.close();
    System.out.println("Hello");
    %>
    </body>
    </html>
  5. 启动 Tomcat 运行

Mybatis 的事务

事务

访问并可能更新数据库中各种数据项的一个程序执行单元(unit)

例如:在关系数据库中,一个事务可以是一条SQL语句,一组SQL语句或整个程序

事务的四大特性ACID

原子性(atomicity):一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

一致性(consistency):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。

隔离性(isolation):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。

持久性(durability):持久性也称永久性(permanence),指事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

不考虑隔离性会产生的3个问题

  1. 脏读:在一个事务处理过程里读取了另一个未提交的事务中的数据。

  2. 不可重复读:在一个事务里面读取了两次某个数据,读出来的数据不一致。这是由于在查询间隔,被另一个事务修改并提交了。

    例如事务T1在读取某一数据,而事务T2立马修改了这个数据并且提交事务给数据库,事务T1再次读取该数据就得到了不同的结果,发送了不可重复读。

  3. 幻读(虚读):在一个事务里面的操作中发现了未被操作的数据。

    幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。

      幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。

解决办法:四种隔离级别

读未提交:一个事务可以读取另一个未提交事务的数据。什么问题都解决不了

读已提交:就是一个事务要等另一个事务提交后才能读取数据。可以防脏读,不能防不可重复读和幻读。

可重复读:在开始读取数据(事务开启)时,不再允许修改操作。可以解决不可重复读问题。

串行化:直到一个事务的所有子事务全部结束才可以执行下一个事务。是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。

JDBC中事务的回顾

在 JDBC 中我们可以通过手动方式将事务的提交改为手动方式,通过 setAutoCommit() 方法就可以调整。 通过 JDK 文档,我们知道该方法的作用如下:

将此连接的自动提交模式设置为给定状态。

如果连接处于自动提交模式,则其所有SQL语句将作为单个事务执行并提交。

否则,它的SQL语句被分组成通过调用方法commit或方法rollback 。 默认情况下,新连接处于自动提交模式。

那么我们的 Mybatis 框架因为是对 JDBC 的封装,所以 Mybatis 框架的事务控制方式,本身也是用 JDBC的 setAutoCommit()方法来设置事务提交方式的。

Mybatis 中事务提交方式

  • 默认为手动提交:

    之前的 CUD 操作过程中,我们都要手动进 行事务的提交,原因是 setAutoCommit()方法在执行时它的值被设置为 false 了,所以我们在 CUD 操作中, 必须通过 sqlSession.commit()方法来执行提交操作。

  • 自动提交事务的设置

1
2
3
4
5
6
7
8
9
10
11
12
13
@Before//在测试方法执行之前执行 
public void init()throws Exception {
//1.读取配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建构建者对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//3.创建 SqlSession 工厂对象
factory = builder.build(in);
//4.创建 SqlSession 对象
session = factory.openSession(true); //这里设置为true即可
//5.创建 Dao 的代理对象
userDao = session.getMapper(IUserDao.class);
}

此时事务就设置为自动提交了,同样可以实现CUD操作时记录的保存。

虽然这也是一种方式,但就编程而言,设置为自动提交方式为 false再根据情况决定是否进行提交,这种方式更常用。因为我们可以根据业务情况来决定提交是否进行提交。

Mybatis 基于XML配置的动态 SQL 语句使用

Mybatis 的映射文件中,前面我们的 SQL 都是比较简单的,有些时候业务逻辑复杂时,我们的 SQL 是动态变 化的,此时在前面的学习中我们的 SQL 就不能满足要求了。

动态 SQL 之<if>标签

需求:

根据实体类的不同取值,使用不同的 SQL 语句来进行查询

比如:在 id 如果不为空时可以根据 id 查询, 如果 username 不同空时还要加入用户名作为条件。这种情况在我们的多条件组合查询中经常会碰到。

  1. 持久层 Dao 接口
1
2
3
4
5
6
/**
* 根据传入的查询条件查询
* @param user 查询条件,用户名、性别、地址等信息可能有也可能没有
* @return
*/
List<User> findByCondition(User user);
  1. 持久层 Dao 映射配置
1
2
3
4
5
6
7
8
9
10
11
12
13
<!--  根据条件查询 -->
<!-- <if>标签的 test 属性中写的是对象的属性名,如果是包装类的对象要使用 OGNL 表达式的写法 -->
<select id="findByCondition" parameterType="com.conv.domain.User" resultType="com.conv.domain.User">
select * from user
<where>
<if test="username != null">
and username = #{username}
</if>
<if test="sex != null">
and sex = #{sex}
</if>
</where>
</select>
  1. 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 测试根据条件查询
*
* @throws IOException
*/
@Test
public void testFindByCondition() throws IOException {
User u = new User();
u.setUsername("老王");
u.setSex("女");

List<User> users = userDao.findByCondition(u);
for (User user : users) {
System.out.println(user);
}
}

动态 SQL 之<foreach>标签

需求:

传入多个 id 查询用户信息,用下边两个 sql 实现:

SELECT * FROM USERS WHERE username LIKE '%张%' AND (id=10 OR id=89 OR id=16)

SELECT * FROM USERS WHERE username LIKE '%张%' AND id IN (10,89,16)

这样我们在进行范围查询时,就要将一个集合中的值,作为参数动态添加进来。将如何进行参数的传递?

  1. 在 QueryVo 中加入一个 List 集合用于封装参数
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.conv.domain;

import java.util.List;

public class QueryVo {
private User user;
private List<Integer> ids;

public User getUser() {
return user;
}

public void setUser(User user) {
this.user = user;
}

public List<Integer> getIds() {
return ids;
}

public void setIds(List<Integer> ids) {
this.ids = ids;
}
}
  1. 持久层 Dao 接口
1
2
3
4
5
6
/**
* 根据QueryVo中提供的id集合,查询用户信息
* @param vo
*@return
*/
List<User> findUserInIds(QueryVo vo);
  1. 持久层 Dao 映射配置
1
2
3
4
5
6
7
8
9
10
11
<!--  根据QueryVo中提供的id集合,查询用户信息 -->
<select id="findUserInIds" parameterType="com.conv.domain.QueryVo" resultType="com.conv.domain.User">
select * from user
<where>
<if test="ids != null and ids.size() > 0">
<foreach collection="ids" open="and id in (" close=")" item="uid" separator=",">
#{uid}
</foreach>
</if>
</where>
</select>

SQL 语句:

select 字段 from user where id in (?)

<foreach>标签用于遍历集合,它的属性:

  • collection:代表要遍历的集合元素,注意编写时不要写 #{}
  • open:代表语句的开始部分
  • close:代表结束部分
  • item:代表遍历集合的每个元素,生成的变量名
  • sperator:代表分隔符
  1. 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 测试foreach标签的使用
*
* @throws IOException
*/
@Test
public void testFindInIds() throws IOException {
QueryVo vo = new QueryVo();
List<Integer> list = new ArrayList<Integer>();
list.add(41);
list.add(42);
list.add(43);
list.add(100);
vo.setIds(list);

List<User> users = userDao.findUserInIds(vo);
users.forEach(System.out::println);
}

Mybatis 中简化编写的 SQL 片段

Sql 中可将重复的 sql 提取出来,使用时用 include 引用即可,最终达到 sql 重用的目的。

  1. 定义代码片段
1
2
3
4
<!--  抽取重复的sql语句 -->
<sql id="defaultUser">
select * from user
</sql>
  1. 引用代码片段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--配置查询所有-->
<!--id是dao的方法名称,不能随便写-->
<select id="findAll" resultType="com.conv.domain.User">
<include refid="defaultUser"></include>
</select>

<!-- 根据QueryVo中提供的id集合,查询用户信息 -->
<select id="findUserInIds" parameterType="com.conv.domain.QueryVo" resultType="com.conv.domain.User">
<include refid="defaultUser"></include>
<where>
<if test="ids != null and ids.size() > 0">
<foreach collection="ids" open="and id in (" close=")" item="uid" separator=",">
#{uid}
</foreach>
</if>
</where>
</select>

注意:

sql 语句后面的分号可加可不加,但是提取的 sql 语句一定不能加分号,因为提取出来的语句可能需要与其他 sql 语句进行拼接,加分号会导致错误,所以建议都不加分号

Mybatis 中的多表操作

表之间的关系:一对多、多对一、一对一、多对多

特例:一个用户对应多个订单,如果拿出每一个订单,它只能属于一个用户,所以 Mybatis 就把多对一看成了一对一

一对一、一对多

示例:用户和账户

一个用户可以有多个账户,一个账户只能属于一个用户(多个账户也可以属于同一个用户)

步骤:

  1. 建立两张表:用户表,账户表,让用户表和账户表之间具备一对多的关系:需要使用外键在账户表中添加

sql

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
DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (
`id` int(11) NOT NULL auto_increment,
`username` varchar(32) NOT NULL COMMENT '用户名称',
`birthday` datetime default NULL COMMENT '生日',
`sex` char(1) default NULL COMMENT '性别',
`address` varchar(256) default NULL COMMENT '地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert into `user`(`id`,`username`,`birthday`,`sex`,`address`) values (41,'老王','2018-02-27 17:47:08','男','北京'),(42,'小二王','2018-03-02 15:09:37','女','北京金燕龙'),(43,'小二王','2018-03-04 11:34:34','女','北京金燕龙'),(45,'传智播客','2018-03-04 12:04:06','男','北京金燕龙'),(46,'老王','2018-03-07 17:37:26','男','北京'),(48,'小马宝莉','2018-03-08 11:44:00','女','北京修正');


DROP TABLE IF EXISTS `account`;

CREATE TABLE `account` (
`ID` int(11) NOT NULL COMMENT '编号',
`UID` int(11) default NULL COMMENT '用户编号',
`MONEY` double default NULL COMMENT '金额',
PRIMARY KEY (`ID`),
KEY `FK_Reference_8` (`UID`),
CONSTRAINT `FK_Reference_8` FOREIGN KEY (`UID`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert into `account`(`ID`,`UID`,`MONEY`) values (1,46,1000),(2,45,1000),(3,46,2000);
  1. 建立两个实体类:用户实体类和账户实体类,让用户和账户的实体类能体现出来一对多的关系

通过面向对象的(has a)关系可以得知,我们可以在 Account 类中加入一个 User 类的对象来代表这个账户 是哪个用户的。

User.java

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
60
61
62
63
64
65
66
67
68
69
70
71
package com.conv.domain;

public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;

// 一对多关系映射:主表实体应该包含从表实体的集合引用
private List<Account> accounts;

public List<Account> getAccounts() {
return accounts;
}

public void setAccounts(List<Account> accounts) {
this.accounts = accounts;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public Date getBirthday() {
return birthday;
}

public void setBirthday(Date birthday) {
this.birthday = birthday;
}

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", birthday=" + birthday +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
}

account.java

因为 Account 类中包含了一个 User 类的对象,它可以封装账户所对应的用户信息

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
package com.conv.domain;

public class Account implements Serializable {

private Integer id;
private Integer uid;
private Double money;

// 一对一关系映射:从表实体应该包含一个主表实体的对象引用
private User user;

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public Integer getUid() {
return uid;
}

public void setUid(Integer uid) {
this.uid = uid;
}

public Double getMoney() {
return money;
}

public void setMoney(Double money) {
this.money = money;
}

public User getUser() {
return user;
}

public void setUser(User user) {
this.user = user;
}

@Override
public String toString() {
return "Account{" +
"id=" + id +
", uid=" + uid +
", money=" + money +
", user=" + user +
'}';
}
}

DAO:

src\main\java\com\conv\dao\IAccountDao.java

1
2
3
4
5
6
7
8
9
10
package com.conv.dao;

public interface IAccountDao {

/**
* 查询所有账户,同时还要获取到当前账户的所属用户信息
* @return
*/
List<Account> findAll();
}
  1. 建立两个配置文件:用户的配置文件、账户的配置文件。并实现配置:

    1. 一对一查询(多对一):当我们查询账户时,可以同时得到账户的所属用户信息

    2. 一对多查询:当我们查询用户时,可以同时得到用户下所包含的账户信息

注意:

因为一个账户信息只能供某个用户使用

所以从查询账户信息出发关联查询用户信息为一对一查询

如果从用户信息出发查询用户下的账户信息则为一对多查询,因为一个用户可以有多个账户

一对一查询

分析:

当我们查询账户时,可以同时得到账户的所属用户信息。

  1. 要实现查询所有账户,同时还要获取到当前账户的所属用户信息,先写出其 SQL 语句:
1
SELECT a.*, u.username, u.address FROM account a, `user` u WHERE u.id = a.uid

我们可以使用 resultMap,定义专门的 resultMap 用于映射一对一查询结果。

  1. 定义 AccountDao.xml 文件:

src\main\resources\com\conv\dao\IAccountDao.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
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace是Dao的全限定类名-->
<mapper namespace="com.conv.dao.IAccountDao">

<!--定义封装account和user的resultMap-->
<resultMap id="accountUserMap" type="account">
<id property="id" column="aid"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
<!-- 一对一的关系映射,配置封装user的内容 -->
<!-- javaType用于提示封装到那个对象(好像删了也没事) -->
<association property="user" column="uid" javaType="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="address" column="address"/>
<result property="sex" column="sex"/>
<result property="birthday" column="birthday"/>
</association>
</resultMap>

<!--配置查询所有-->
<!--id是dao的方法名称,resultMap是resultMap的id-->
<select id="findAll" resultMap="accountUserMap">
SELECT a.*, u.username, u.address FROM account a, `user` u WHERE u.id = a.uid
</select>

</mapper>
  1. 测试

src\test\java\com\conv\test\AccountTest.java

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
package com.conv.test;

public class AccountTest {
private InputStream in;
private SqlSession session;
IAccountDao accountDao;

@Before //用于在测试方法执行之前执行
public void init() throws IOException {
//1. 读取配置文件,生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2. 获取SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//3. 获取SqlSession对象
session = factory.openSession();
//4. 获取dao的代理对象
accountDao = session.getMapper(IAccountDao.class);
}

@After //用于在测试方法执行之后执行
public void destroy() throws IOException {
//提交事务
session.commit();
// 关闭资源
session.close();
in.close();
}

/**
* 测试查询所有
*
* @throws IOException
*/
@Test
public void testFindAll() {
List<Account> accounts = accountDao.findAll();
accounts.forEach(System.out::println);
}
}

一对多查询

分析:

当我们查询用户时,可以同时得到用户下所包含的账户信息

  1. 先写出其 SQL 语句:
1
SELECT * FROM `user` u LEFT OUTER JOIN account a on u.id = a.UID

左外连接会返回左表的所有数据

  1. 定义 UserDao.xml 文件:

src\main\resources\com\conv\dao\IUserDao.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
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace是Dao的全限定类名-->
<mapper namespace="com.conv.dao.IUserDao">

<!-- 定义User的resultMap -->
<resultMap id="userAccountMap" type="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="address" column="address"/>
<result property="sex" column="sex"/>
<result property="birthday" column="birthday"/>

<!-- 配置user对象中 accounts 集合的映射 -->
<!-- ofType是集合中元素的类型 -->
<collection property="accounts" ofType="account">
<id property="id" column="aid"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
</collection>
</resultMap>

<!--配置查询所有-->
<!--id是dao的方法名称,不能随便写-->
<select id="findAll" resultMap="userAccountMap">
SELECT * FROM `user` u LEFT OUTER JOIN account a on u.id = a.UID
</select>

</mapper>
  1. 测试

src\test\java\com\conv\test\UserTest.java

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
package com.conv.test;

public class UserTest {
private InputStream in;
private SqlSession session;
IUserDao userDao;

@Before //用于在测试方法执行之前执行
public void init() throws IOException {
//1. 读取配置文件,生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2. 获取SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//3. 获取SqlSession对象
session = factory.openSession();
//4. 获取dao的代理对象
userDao = session.getMapper(IUserDao.class);
}

@After //用于在测试方法执行之后执行
public void destroy() throws IOException {
//提交事务
session.commit();
// 关闭资源
session.close();
in.close();
}

/**
* 测试查询所有
*
* @throws IOException
*/
@Test
public void testFindAll() {
List<User> users = userDao.findAll();
users.forEach(user -> {
System.out.println(user);
System.out.println(user.getAccounts());
});
}

}

多对多

示例:用户和角色

一个用户可以有多个角色,一个角色可以赋予多个用户

步骤:

  1. 建立两张表:用户表,角色表,让用户表和角色表具有多对多的关系。需要使用中间表,中间表中包含各自的主键,在中间表中是外键。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
DROP TABLE IF EXISTS `role`;

CREATE TABLE `role` (
`ID` int(11) NOT NULL COMMENT '编号',
`ROLE_NAME` varchar(30) default NULL COMMENT '角色名称',
`ROLE_DESC` varchar(60) default NULL COMMENT '角色描述',
PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert into `role`(`ID`,`ROLE_NAME`,`ROLE_DESC`) values (1,'院长','管理整个学院'),(2,'总裁','管理整个公司'),(3,'校长','管理整个学校');


DROP TABLE IF EXISTS `user_role`;

CREATE TABLE `user_role` (
`UID` int(11) NOT NULL COMMENT '用户编号',
`RID` int(11) NOT NULL COMMENT '角色编号',
PRIMARY KEY (`UID`,`RID`),
KEY `FK_Reference_10` (`RID`),
CONSTRAINT `FK_Reference_10` FOREIGN KEY (`RID`) REFERENCES `role` (`ID`),
CONSTRAINT `FK_Reference_9` FOREIGN KEY (`UID`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert into `user_role`(`UID`,`RID`) values (41,1),(45,1),(41,2);
  1. 建立两个实体类:用户实体类和角色实体类,让用户和角色的实体类能体现出来多对多的关系,各自包含对方一个集合引用

src\main\java\com\conv\domain\User.java

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
60
61
62
63
64
65
66
67
68
69
70
71
package com.conv.domain;

public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;

//多对多的关系映射:一个用户可以具备多个角色
private List<Role> roles;

public List<Role> getRoles() {
return roles;
}

public void setRoles(List<Role> roles) {
this.roles = roles;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public Date getBirthday() {
return birthday;
}

public void setBirthday(Date birthday) {
this.birthday = birthday;
}

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", birthday=" + birthday +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
}

src\main\java\com\conv\domain\Role.java

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
package com.conv.domain;

public class Role implements Serializable {

private Integer roleId;
private String roleName;
private String roleDesc;

//多对多的关系映射,一个角色可以赋予多个用户
private List<User> users;

public List<User> getUsers() {
return users;
}

public void setUsers(List<User> users) {
this.users = users;
}

public Integer getRoleId() {
return roleId;
}

public void setRoleId(Integer roleId) {
this.roleId = roleId;
}

public String getRoleName() {
return roleName;
}

public void setRoleName(String roleName) {
this.roleName = roleName;
}

public String getRoleDesc() {
return roleDesc;
}

public void setRoleDesc(String roleDesc) {
this.roleDesc = roleDesc;
}

@Override
public String toString() {
return "Role{" +
"roleId=" + roleId +
", roleName='" + roleName + '\'' +
", roleDesc='" + roleDesc + '\'' +
'}';
}
}

持久层接口 src\main\java\com\conv\dao\IRoleDao.java

1
2
3
4
5
package com.conv.dao;

public interface IRoleDao {
List<Role> findAll();
}
  1. 建立两个配置文件:用户的配置文件,角色的配置文件

    实现配置:

    • 当我们查询用户时,可以同时得到用户所包含的角色信息;
    • 当我们查询角色时,可以同时得到角色的所赋予的用户信息

分析:

查询用户:

我们需要用到Role表,但角色分配的用户的信息我们并不能直接找到用户信息,而是要通过中间表(USER_ROLE 表)才能关联到用户信息。

写出其SQL语句

1
2
3
SELECT u.*,r.id as rid,r.role_name,r.role_desc FROM `user` u
LEFT OUTER JOIN user_role ur on u.id = ur.UID
LEFT OUTER JOIN role r on r.ID = ur.RID

查询角色:

我们需要用到Role表,但角色分配的用户的信息我们并不能直接找到用户信息,而是要通过中间表(USER_ROLE 表)才能关联到用户信息。

写出其SQL语句

1
2
3
SELECT u.*,r.id as rid,r.role_name,r.role_desc FROM role r
LEFT OUTER JOIN user_role ur on r.ID = ur.RID
LEFT OUTER JOIN `user` u on u.id = ur.UID

建立两个配置文件

src\main\resources\com\conv\dao\IUserDao.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
31
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace是Dao的全限定类名-->
<mapper namespace="com.conv.dao.IUserDao">

<!-- 定义User的resultMap -->
<resultMap id="userMap" type="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="address" column="address"/>
<result property="sex" column="sex"/>
<result property="birthday" column="birthday"/>

<!-- 配置角色集合的映射 -->
<collection property="roles" ofType="role">
<id property="roleId" column="rid"/>
<result property="roleName" column="role_name"/>
<result property="roleDesc" column="role_desc"/>
</collection>
</resultMap>

<!--配置查询所有-->
<!--id是dao的方法名称,不能随便写-->
<select id="findAll" resultMap="userMap">
SELECT u.*,r.id as rid,r.role_name,r.role_desc FROM `user` u
LEFT OUTER JOIN user_role ur on u.id = ur.UID
LEFT OUTER JOIN role r on r.ID = ur.RID
</select>

</mapper>

src\main\resources\com\conv\dao\IRoleDao.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
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace是Dao的全限定类名-->
<mapper namespace="com.conv.dao.IRoleDao">

<!-- 定义Role的resultMap -->
<resultMap id="roleMap" type="role">
<id property="roleId" column="rid"/>
<result property="roleName" column="role_name"/>
<result property="roleDesc" column="role_desc"/>

<collection property="users" ofType="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="address" column="address"/>
<result property="sex" column="sex"/>
<result property="birthday" column="birthday"/>
</collection>
</resultMap>

<!--配置查询所有-->
<!--id是dao的方法名称,不能随便写-->
<select id="findAll" resultMap="roleMap">
SELECT u.*,r.id as rid,r.role_name,r.role_desc FROM role r
LEFT OUTER JOIN user_role ur on r.ID = ur.RID
LEFT OUTER JOIN `user` u on u.id = ur.UID
</select>

</mapper>
  1. 测试
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
// src\test\java\com\conv\test\UserTest.java
/**
* 测试查询所有
*
* @throws IOException
*/
@Test
public void testFindAll() {
List<User> users = userDao.findAll();
users.forEach(user -> {
System.out.println("-----每个用户的信息-----");
System.out.println(user);
System.out.println(user.getRoles());
});
}

// src\test\java\com\conv\test\RoleTest.java
/**
* 测试查询所有
*
* @throws IOException
*/
@Test
public void testFindAll() {
List<Role> roles = roleDao.findAll();
roles.forEach(role->{
System.out.println("----每个角色的信息-----");
System.out.println(role);
System.out.println(role.getUsers());
});
}
坚持原创技术分享,感谢您的支持和鼓励!