学习笔记:javaEE基础10 MyBatis

学习来源 通过自动回复机器人学Mybatis—基础版

MyBatis的基础知识,暂时放弃了就业班的课程,追源生活老师的课程去了

关于项目分层

大体上为MVC,但是这里需要对M层,也就是操作数据库的模块在分层,分为 daodb 层,前者不用说了,用来向 service 提供操作数据的方法,而新加进来的后者则是专门负责和数据库交互,使用了 MyBatis 之后,这个模块主要负责向 dao 提供 SqlSession 以及 MyBatis 相关文件配置,包括数据库连接配置文件,Sql配置文件,这样子层次就更加分明了。


对MyBatis的印象

一些基础功能用下来,感觉就像是对sql功能的加强,为其添加了判断,循环等特性,就像是css的增强版scss一样;不同于在java中对sql的拼接,它可以更加智能地处理sql语句,比如没有数据时自动 WHERESET,自动加逗号等分隔符,同时提供直接向查询结果和java bean映射的功能,甚至包括一些复杂的数据结构。其实吧,这些功能都可以自己封装的,但是毕竟都要花时间,有工具帮我们做了岂不美哉。

同时直接将sql语句写在文件中,和java逻辑代码解耦,有利有弊吧,不方便调试,程序自动组装sql时也会出现各种奇葩的错误,要调试好半天。

这里使用的版本是3.4.6


基础配置

主配置文件

主配置文件 Configuration.xml(文件名字随便写)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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">
</transactionManager>
<dataSource type="UNPOOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value=""/>
<property name="username" value=""/>
<property name="password" value=""/>
</dataSource>
</environment>
</environments>
</configuration>

对于 dataSource 标签内的参数就是在配JDBC时需要填的连接数据库的一些参数,驱动,url,用户名和密码,至于它的属性 type 是然你选择是否使用连接池,这里不使用

读入配置文件

db 中获取SqlSession的类中编写载入配置文件的程序(目前不是很清楚 SqlSessionFactory 是否要做成单例,这里没有使用单例模式),为了图方便把测试用例也写在一起了

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
public class ListMessageDb {
/**
* 获取sessionsql
* @return
* @exception Exception
*/

public static ListMessageDb getInstance() {
return new ListMessageDb();
}

public SqlSession getSqlSession() throws Exception{
// 1. 读取配置文件获取信息
Reader reader = Resources.getResourceAsReader("com/example/microapp/db/Configuration.xml");

// 2. 通过配置信息构建一个SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

// 3. 通过 SQLSessionFactory打开一个数据库会话
SqlSession sqlSession = sqlSessionFactory.openSession();

return sqlSession;
}

@Test
public void testSqlSession() {
try {
if (ListMessageDb.getInstance().getSqlSession() == null) {
System.out.println("打开session失败");
} else {
System.out.println("打开session成功");
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("打开session失败");
}
}
}

注意, Resources.getResourceAsReader中的值就配置文件在classpath下的路径。

添加日志打印

因为是读入下载配置文件中的sql,不方便断点调试,幸好它原生支持log4j日志打印功能,在classpath的根路径,也就是你工程package的根路径下添加log4j配置文件就行了,同时将它的jar包加入到classpath中,在官网下载的压缩文件中就有;需要熟悉log4j的相关配置,这个是我直接复制老师的代码的

1
2
3
4
5
6
7
8

log4j.rootLogger=DEBUG,Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d[%t]%-5p[%c]-%m/%n
log4j.appender.Console.encoding=UTF-8
log4j.appender.A1.Encoding=UTF-8
log4j.logger.org.apache=INFO

打印的日志级别的DEBUG,是最详细的,打印到Conole中,倒数第二三行的功能是防止出现乱码,出现乱码有几种情况,但都是业务流程流程中编码不一致导致的,请同意编码格式

  • request传入数据的编码,请设置 setCharacterEncoding() 为相关编码
  • IDE的console的编码
  • java编译器对编码设置,设置 -Dfile.encoding=为相关编码
  • IDE的编码格式
  • 打印程序输出的编码,上面的就是对此的设置
  • 数据库的编码,包括database,table的相关编码

一些基础sql

准备

建立数据表

1
2
3
4
5
6
7
CREATE TABLE `message` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`command` varchar(16) DEFAULT NULL COMMENT '指令名称',
`description` varchar(32) DEFAULT NULL COMMENT '描述',
`comment` varchar(2048) DEFAULT NULL COMMENT '内容',
PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8

测试数据的创建这里就不列出来了

建立对于的javabean,其中的注释,toString,getter和setter就都不列出了,太占位置,注意和列名尽量保持一致,或者把注释写好,方便人阅读;这是专门为数据库处理封装的对象,在service层和view层可以使用DTO对象

1
2
3
4
5
6
7
8
9
public class Message {
private int id;
private String command;
private String description;
private String content;
public Message() {
}
// ...........
}

db 层中为其创建相应的xml文件,用于配置映射对象以及填写相应的sql语句,文件起名为 Message.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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="Message">
<resultMap type="com.example.microapp.bean.Message" id="MessageResult">
<!-- jdbcType 是在 java.sql.Types.* 中的-->
<id column="id" jdbcType="INTEGER" property="id"/>
<result column="command" jdbcType="VARCHAR" property="command"/>
<result column="description" jdbcType="VARCHAR" property="description"/>
<result column="content" jdbcType="VARCHAR" property="content"/>
</resultMap>
</mapper>

resultMap 用于对查询结果的映射,MyBatis将查询结果ResultSet直接映射为 resultMap中配置的内容,返回一个列表对象,resultMap 的属性 id 用于最为其唯一的表示,和html中的id标签差不都,必须唯一,但是这里,它的外层标签的属性为 nampspace,属性c++的人应该都不会陌生,这是作用域,所以之前的 id 只要在这个作用域中唯一就可以了,再别的命名空间中调用的话可以这样 Message.MessageResult,而 type 属性就是需要映射的类的引用包名。

内部标签id 代表数据表中的主键,而result 就是别的列,他们的属性常用属性如下

  • column 数据表列名,注意,这里指的是查询返回的那个table的列名,默认使用table的列名,但也可以取别名,也就是说,你也可以用别的名字,但是在sql语句中要给相应的列取相同的别名
  • jdbcType 数据库数据类型,正如注释中写的那样,就是 java.sql.Types.* 中的值
  • property 映射类的属性名称

编写完成之后需要在之前的主配置文件 Configuration.xml 中声明一下

1
2
3
4
5
6
7
8
9
10
11
....
<?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>
......
<mappers>
<mapper resource="com/example/microapp/db/Message.xml"/>
</mappers>
</configuration>

填写的也是该配置文件在classpath中的路径,千万别忘记注册了,配置文件一多起来可能就会忘记,如果开发过Angular2的人一定会记得每次添加一个component之后都要在一个配置文件中注册,否则会报错,幸好ng有完善的命令行自动化工具,使用它创建component可以自动帮你注册,但是可就MyBatis没有那么方便了…


select语句

普通的select语句谁不会写,这里写一个模糊匹配,需求是:

  • 根据传入的 javabean中的 commanddescription 查询相应的结果
  • 如果 commanddescription 为空字串或为null,则不作为查询条件
  • 使用 ‘%’ 进行模糊匹配

在Message.xml中这样书写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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="Message">
......
<select id="queryMessageList" parameterType="com.example.microapp.bean.Message"
resultMap="MessageResult">
SELECT id, command, description, content
FROM message
<where>
<if test="command != null and command.length() != 0">
AND command LIKE CONCAT( '%', #{command}, '%' )
</if>
<if test="description != null and description.length() != 0">
AND description LIKE CONCAT( '%', #{description}, '%' )
</if>
</where>
</select>
</mapper>

查询语句是在 select 总书写的,使用到的属性如下

  • id 在java中调用此功能时名字,和之前的resultMapid一样,保证在命名空间中保持唯一
  • parameterType 在java中调用此功能是传入的参数的类的引用包名,注意,参数只能传入一个,在必要的时候进行封装,传入list或者map时就写它的引用包名就行了,详细请参考官方文档
  • resultMap 返回结果的映射,之前已经配置过,添写对应的id号就行了

而在标签中就写sql语句就行了。

但是,根据需求需要动态拼接sql语句,出现了是否需要 ‘WHERE’ 和 ‘AND’ 的问题,因为如果传入的参数都是null的话就不要‘WHERE’,那也就不要 ‘AND’,反正逻辑很复杂,但是MyBatis提供的标签就解决了这个问题

使用 where 标签,既可以帮我们自动判断是否要加‘WHERE’,是否要加‘AND’

使用 #{} 可以直接从传入的bean对象中获取其属性,它和 ${} 的区别是前者支持预编译,而后者的功能和JDBC中直接拼接sql一样,类似于es6中的同名记号

使用模糊匹配是用别的方法拼接都会出现奇怪的错误,只有使用sql中的 CONCAT 是正常的,反正我是很不能理解,明明和老师的代码一样却会报错,昨天调试了好久都没弄出来

if 标签就不同解释了,和jstl中一样

在java代码中调用如下

1
2
3
4
5
6
public List<Message> queryMessageList(Message message) throws Exception{
SqlSession sqlSession = ListMessageDb.getInstance().getSqlSession();
List<Message> result = (ArrayList)sqlSession.selectList("Message.queryMessageList", message);
sqlSession.close();
return result;
}

首先获取 SqlSession,在调用它的 selectList,传入的参数第一个就是Message.xml配置文件中写的那个 select 标签所在的命名空间和id号

注意,这样的异常不能抛出,必须处理,否则出现异常能会出现sqlSession没有关闭的情况,就像是JDBC中connection没有关闭一样,问题很严重。因为这是个demo,图方便就这样子写了

编写测试文件

1
2
3
4
5
6
7
8
9
10
11
@Test
public void queryMessageList() throws Exception {
Message message = new Message();
message.setCommand("");
message.setDescription("精彩");

List<Message> resultList= MessageListDaoImpl.getInstance().queryMessageList(message);
resultList.forEach((v) -> {
System.out.println(v);
});
}

打印如下

1
2
3
4
5
2019-01-10 10:40:56,179[main]DEBUG[Message.queryMessageList]-==>  Preparing: SELECT id, command, description, content FROM message WHERE description LIKE CONCAT( '%', ?, '%' ) /
2019-01-10 10:40:56,230[main]DEBUG[Message.queryMessageList]-==> Parameters: 精彩(String)/
2019-01-10 10:40:56,272[main]DEBUG[Message.queryMessageList]-<== Total: 2/
Message{id=1, command='查看', description='精彩内容', content='精彩内容'}
Message{id=2, command='段子', description='精彩段子', content='如果你..'}

第一行为prepare好的sql语句,相当于是JDBC中从connection中获取preparestatement,第二行是传入的参数,相当于是JDBC中的preparestatement的set方法,第三行是获取的结果,相当于是JDBC中的executeQuery,获取ResultSet,最后打印结果。

那么什么command和description都不设置值呢?

1
2
3
4
5
6
7
8
9
10
11
@Test
public void queryMessageList() throws Exception {
Message message = new Message();
message.setCommand("");
message.setDescription("");

List<Message> resultList= MessageListDaoImpl.getInstance().queryMessageList(message);
resultList.forEach((v) -> {
System.out.println(v);
});
}

结果如下:

1
2
3
4
5
2019-01-10 10:48:14,935[main]DEBUG[Message.queryMessageList]-==>  Preparing: SELECT id, command, description, content FROM message /
2019-01-10 10:48:14,980[main]DEBUG[Message.queryMessageList]-==> Parameters: /
2019-01-10 10:48:15,026[main]DEBUG[Message.queryMessageList]-<== Total: 15/
Message{id=1, command='查看', description='精彩内容', content='精彩内容'}
......

从第一行可以看到,并没有 WHERE 语句了,而最终查到了所有的15条数据,这里就不全部粘贴出来了


delete语句

和之前一样,普通的delete语句写的没意思,需求是根据id号批量删除Message,要使用到sql中的 IN 语句

在Message.xml中这样写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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="Message">
<select id="deleteMessagesById" parameterType="java.util.List">
DELETE FROM message
WHERE id IN
<foreach collection="list" item="item"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
</mapper>

使用 parameterType 不用在xml中写映射,标签 foreach 可以对传入的list或者map进行遍历,其中 item就是每一个的值,这里我们传入的是存有id号的list,而 IN 语句有开闭括号,每个数值之间还有逗号作为分割,如果用普通的JDBC,会要加多个判断,比如最后一个值后面不能有逗号;这里这个标签一起为我们打包解决了

编写java代码调用

1
2
3
4
5
6
7
8
public int deleteMessageByIds(List<Integer> ids) throws Exception {
SqlSession sqlSession = null;
sqlSession = ListMessageDb.getInstance().getSqlSession();
int result = sqlSession.delete("Message.deleteMessagesById", ids);
sqlSession.commit();
sqlSession.close();
return result;
}

注意,对于像是 UPDATE, INSERT, DELETE 这样的DML语句,Mybatis和JDBC不同,需要我们自己commit,当然,这里的异常也是需要捕获的。

编写测试用例

1
2
3
4
5
6
7
8
9
@Test
public void deleteMessageByIds() throws Exception {
List<Integer> ids = new ArrayList<>();
ids.add(22);
ids.add(23);
ids.add(24);
int result = MessageListDaoImpl.getInstance().deleteMessageByIds(ids);
System.out.println(result);
}

输出结果如下

1
2
3
4
2019-01-10 11:02:11,486[main]DEBUG[Message.deleteMessagesById]-==>  Preparing: DELETE FROM message WHERE id IN ( ? , ? , ? ) /
2019-01-10 11:02:11,541[main]DEBUG[Message.deleteMessagesById]-==> Parameters: 22(Integer), 23(Integer), 24(Integer)/
2019-01-10 11:02:11,542[main]DEBUG[Message.deleteMessagesById]-<== Updates: 3/
3

可以看出,成功删除了三行数据,当要删除的id不存在时会自动忽略,不过感觉最好在业务逻辑里先验证一下


insert 语句

这里有一个需求,要求存入数据库的Message的值的id使用的是数据库auto_increment的值

1
2
3
4
5
6
7
8
9
10
11
12
<?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="Message">
<insert id="insertMessage" useGeneratedKeys="true" keyProperty="id" parameterType="java.util.List">
INSERT INTO message(command, description, content) VALUES
<foreach collection="list" item="item" separator=",">
(#{item.command}, #{item.description}, #{item.content})
</foreach>
</insert>
</mapper>

这里 insert 标签多了一些属性

  • useGeneratedKeys 使用数据库主键自动生成的数据
  • keyProperty 主键的列名

这样子的话需要插入的就是非主键的列了

对于 foreach 标签里面每个item如果是javabean的话可以想上面的代码一样直接取它的属性

编写java程序调用

1
2
3
4
5
6
7
public int insertMessages(List<Message> lists) throws Exception{
SqlSession sqlSession = ListMessageDb.getInstance().getSqlSession();
int rows = sqlSession.insert("Message.insertMessage", lists);
sqlSession.commit();
sqlSession.close();
return rows;
}

别忘记 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 insertMessages() throws Exception {
List<Message> messages = new ArrayList<>();
Message msg1 = new Message();
msg1.setContent("测试用例一");
msg1.setDescription("测试用例一aaaaa");
msg1.setCommand("测试用例一aaaaa");

Message msg2 = new Message();
msg2.setContent("测试用例二");
msg2.setDescription("测试用例二bbbbbb");
msg2.setCommand("测试用例二bbbbbb");

Message msg3 = new Message();
msg3.setContent("测试用例三");
msg3.setDescription("测试用例三cccccc");
msg3.setCommand("测试用例三cccccc");

messages.add(msg1);
messages.add(msg2);
messages.add(msg3);

int result = MessageListDaoImpl.getInstance().insertMessages(messages);
System.out.println(result);
}

答应结果如下

1
2
3
4
2019-01-10 11:45:51,751[main]DEBUG[Message.insertMessage]-==>  Preparing: INSERT INTO message(command, description, content) VALUES (?, ?, ?) , (?, ?, ?) , (?, ?, ?) /
2019-01-10 11:45:51,813[main]DEBUG[Message.insertMessage]-==> Parameters: 测试用例一aaaaa(String), 测试用例一aaaaa(String), 测试用例一(String), 测试用例二bbbbbb(String), 测试用例二bbbbbb(String), 测试用例二(String), 测试用例三cccccc(String), 测试用例三cccccc(String), 测试用例三(String)/
2019-01-10 11:45:51,817[main]DEBUG[Message.insertMessage]-<== Updates: 3/
3

查询数据库,结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.......
*************************** 13. row ***************************
ID: 26
COMMAND: 测试用例一aaaaa
DESCRIPTION: 测试用例一aaaaa
CONTENT: 测试用例一
*************************** 14. row ***************************
ID: 27
COMMAND: 测试用例二bbbbbb
DESCRIPTION: 测试用例二bbbbbb
CONTENT: 测试用例二
*************************** 15. row ***************************
ID: 28
COMMAND: 测试用例三cccccc
DESCRIPTION: 测试用例三cccccc
CONTENT: 测试用例三

UPDATE大同小异,这里就不写了


一对多查询

建立ResultMap映射时这里需要使用到 collection

准备

两个表,tag与content,其中content中存有其对于的tag编号

1
2
3
4
5
CREATE TABLE `tag` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id号',
`name` varchar(255) NOT NULL COMMENT '标签名称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
1
2
3
4
5
6
CREATE TABLE `content` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id号',
`content` varchar(255) NOT NULL COMMENT '内容',
`tagid` int(11) NOT NULL COMMENT '对应的标签号',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;

两个javabean

Tag.java

1
2
3
4
5
6
public class Tag {
private int id;
private String name;
private List<Content> contents;
// .....
}

Content.java

1
2
3
4
5
6
public class Content {
private int id;
private String content;
private int tagId;
// .....
}

对应的XML配置文件

Content.xml

1
2
3
4
5
6
7
8
9
10
11
12
<?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="Content">
<resultMap id="ContentResult" type="com.example.microapp.bean.Content">
<id column="id" jdbcType="INTEGER" property="id"/>
<result column="content" jdbcType="VARCHAR" property="content"/>
<result column="tagid" jdbcType="INTEGER" property="tagId"/>
</resultMap>
</mapper>

Tag.xml

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">

<mapper namespace="Tag">
<resultMap id="TagResult" type="com.example.microapp.bean.Tag">
<id column="a_id" jdbcType="INTEGER" property="id"/>
<result column="name" jdbcType="VARCHAR" property="name"/>
<!-- 表示一个列表 -->
<collection property="contents" resultMap="Content.ContentResult"/>
</resultMap>
</mapper>

主要要在主配置文件总注册,还要编写测试数据,这里就不再写了


代码编写

需求是希望根据传入的tag的名字列表查询其所有值,包括对于的content,存放到类型为Tag的列表中

这里就要对 resultMap 做特殊处理了,主要到前面的 Tag.xml 中有一行注释,下面用到的 collection 标签就是用来处理一对多关系的,properties 对应其javabean中的值,而 resultMap对应的值就是在 Content.xml 中配置的content的ResultMap

当然,在配置文件中id对应的列名写成了a_id,这是因为tag和content表有相同的列名 id,而映射的值需要唯一,所以这里是用了别名,当然在下面的sql语句中也要体现,a.id取了别名 a_id;

sql语句当然是用 LEFT JOIN

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
<?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="Tag">
....
<select id="queryTagList" parameterType="java.util.List" resultMap="TagResult">
SELECT
a.id a_id, a.name, b.id, b.content, b.tagid
FROM
tag a
LEFT JOIN
content b
ON
a.id = b.tagid
<where>
a.id IN
<foreach collection="list" item="item"
open="(" separator="," close=")">
#{item}
</foreach>
</where>
</select>
</mapper>

编写方法调用

1
2
3
4
5
6
7
public List<Tag> queryTagList(List<Integer> tagIds) throws Exception{
SqlSession sqlSession = ListMessageDb.getInstance().getSqlSession();
List<Tag> tags = new ArrayList<>();
tags = sqlSession.selectList("Tag.queryTagList", tagIds);
sqlSession.close();
return tags;
}

编写测试用例,查询编号为1与2的tag数据

1
2
3
4
5
6
7
8
9
10
11
/**
* 查询序号为 1,2的tag
*/
@Test
public void queryTagList() throws Exception {
List<Integer> ids = new ArrayList<>();
ids.add(1);
ids.add(2);
List<Tag> result = MessageListDaoImpl.getInstance().queryTagList(ids);
System.out.println(result);
}

输出结果如下

1
2
3
4
5
6
7
8
9
10
11
12
2019-01-10 12:07:14,395[main]DEBUG[Tag.queryTagList]-==>  Preparing: SELECT a.id a_id, a.name, b.id, b.content, b.tagid FROM tag a LEFT JOIN content b ON a.id = b.tagid WHERE a.id IN ( ? , ? ) /
2019-01-10 12:07:14,437[main]DEBUG[Tag.queryTagList]-==> Parameters: 1(Integer), 2(Integer)/
2019-01-10 12:07:14,466[main]DEBUG[Tag.queryTagList]-<== Total: 8/
[Tag{id=1, name=休闲}Content{id=1, content='1aaa', tagId=1}
Content{id=2, content='1bbb', tagId=1}
Content{id=3, content='1ccc', tagId=1}
Content{id=4, content='1ddd', tagId=1}
, Tag{id=2, name=游戏}Content{id=5, content='2aaa', tagId=2}
Content{id=6, content='2bbb', tagId=2}
Content{id=7, content='2ccc', tagId=2}
Content{id=8, content='2ddd', tagId=2}
]

可以看到,Tag中的列表被成功填写了


多对一查询

建立ResultMap映射时这里需要使用到 association

准备

数据库的content和tag表不变,但是javabean需要改一改了,和之前的反过来,这次是Content里存放Tag数据,具体如下,新建两个文件

TagAss.java

1
2
3
4
5
public class TagAss {
private int id;
private String name;
.....
}

ContentAss.java

1
2
3
4
5
6
public class ContentAss {
private int id;
private String content;
private TagAss tag;
.....
}

xml配置还是在原来的对应的文件这种写,但是记住 resultMapid 千万不能重复

Tag.xml

1
2
3
4
5
6
7
8
9
10
11
12
<?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="Tag">
....
<resultMap id="TagAssResult" type="com.example.microapp.bean.TagAss">
<id column="id" jdbcType="INTEGER" property="id"/>
<result column="name" jdbcType="VARCHAR" property="name"/>
</resultMap>
....
</mapper>

Content.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
<?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="Tag">
......
<resultMap id="ContentResultAss" type="com.example.microapp.bean.ContentAss">
<id column="content_id" jdbcType="INTEGER" property="id"/>
<result column="content" jdbcType="VARCHAR" property="content"/>
<association property="tag" resultMap="Tag.TagAssResult"/>
</resultMap>

<!-- 多对一查询-->
<select id="queryContentList" parameterType="java.util.List" resultMap="ContentResultAss">
SELECT
a.id content_id, a.content, b.id, b.name
FROM
content a
LEFT JOIN
tag b
ON
a.tagid = b.id
<where>
a.id IN
<foreach collection="list" item="item"
open="(" separator="," close=")">
#{item}
</foreach>
</where>
</select>
</mapper>

association 和 前面的collection 非常类似


代码编写

编写dao方法

1
2
3
4
5
public List<ContentAss> queryContentList(List<Integer> contentId) throws Exception {
SqlSession sqlSession = ListMessageDb.getInstance().getSqlSession();
List<ContentAss> result = (ArrayList)sqlSession.selectList("Content.QueryContentList", contentId);
return result;
}

编写测试用例:查询序号为1,3,6,10,11的content

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 查询序号为1,3,6,10,11的content
*/
@Test
public void queryContentList() throws Exception {
List<Integer> contentId = new ArrayList<>();
contentId.add(1);
contentId.add(3);
contentId.add(6);
contentId.add(10);
contentId.add(11);
List<ContentAss> resultList= MessageListDaoImpl.getInstance().queryContentList(contentId);
resultList.forEach((v) -> {
System.out.println(v);
});
}

输出结果如下

1
2
3
4
5
6
7
8
2019-01-10 12:29:52,075[main]DEBUG[Content.QueryContentList]-==>  Preparing: SELECT a.id content_id, a.content, b.id, b.name FROM content a LEFT JOIN tag b ON a.tagid = b.id WHERE a.id IN ( ? , ? , ? , ? , ? ) /
2019-01-10 12:29:52,121[main]DEBUG[Content.QueryContentList]-==> Parameters: 1(Integer), 3(Integer), 6(Integer), 10(Integer), 11(Integer)/
2019-01-10 12:29:52,148[main]DEBUG[Content.QueryContentList]-<== Total: 5/
ContentAss{id=1, content='1aaa', tag=TagAss{id=1, name='休闲'}}
ContentAss{id=3, content='1ccc', tag=TagAss{id=1, name='休闲'}}
ContentAss{id=6, content='2bbb', tag=TagAss{id=2, name='游戏'}}
ContentAss{id=10, content='3bbb', tag=TagAss{id=3, name='体育'}}
ContentAss{id=11, content='3ccc', tag=TagAss{id=3, name='体育'}}

可以看到,ContentAss中的属性TagAss也被成功填写了