Mybatis的使用(二)

简单的CRUD

注解方式这里就不再记录,我开发中使用的是XML方式,两种方式其实语法都差不多。这里我只使用到了dao层,然后通过junit进行单元测试

对Husband表进行简单的增删改查:

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
<?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.example.dao.HusbandDao">

<!--
id:与dao接口中的方法同名
resultType:返回值类型
-->
<select id="findAllHusband" resultType="Husband">
SELECT * FROM husband
</select>

<!--
parameterType:参数类型,单个参数时可以省略
-->
<insert id="addAnHusband" parameterType="Husband">
INSERT INTO husband VALUES (#{hid}, #{hname})
</insert>

<delete id="deleteAHusband">
DELETE FROM husband WHERE hid = #{hid}
</delete>

<update id="updateAHusband">
UPDATE husband SET hname = #{hname} WHERE hid = #{hid}
</update>

</mapper>

其他的单表查询方式就不再记录了,根据业务需要再进行拓展就行

多表查询

多表查询一般分为一对一,一对多,多对多,从实质上来说多对多可以算成两个一对多。

我这里就记录一下使用Mybatis进行多表一对一和一对多的查询。

一共有3张测试表,Husband表,Wife表,Son表。其中Husband表和Wife表是一对一的关系,Husband表和Son表是一对多关系。

下列为实体类的具体代码,这里使用lombok来添加无参,全参构造,setter,getter,toString方法:

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
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class Husband {
private int hid;
private String hname;
private Wife wifes;
private List<Son> sons;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class Wife {
private int wid;
private String wname;
private int hid;
private Husband husband;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class Son {
private int sid;
private String sname;
private int hid;
private Husband husband;
}

通过Husband的hid查询相关信息以及所对应的Wife,方法一:

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
<?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.example.dao.HusbandDao">

<!--
resultMap:结果映射,一般在关联表查询中使用,将查询结果映射到实体类的属性中
type: 指定映射的对象类型
column: 字段名(如果使用as取别名,那就是别名)
property: 实体类中的属性名
association: 一对一关联
javaType: 指定映射的对象类型
select: 根据id调用select标签
-->
<select id="queryHusbandInfoAndWifeById" resultMap="queryHusbandInfoAndWifeByIdMap">
select * FROM husband WHERE hid = #{hid}
</select>
<resultMap id="queryHusbandInfoAndWifeByIdMap" type="Husband">
<id column="hid" property="hid" />
<result column="hname" property="hname" />

<association property="wife" column="hid" javaType="Wife" select="SelectWifeByHid">
<id column="wid" property="wid" />
<result column="wname" property="wname" />
</association>
</resultMap>
<select id="SelectWifeByHid" resultType="Wife">
SELECT * FROM wife WHERE hid = #{hid}
</select>

</mapper>

通过Husband的hid查询相关信息以及所对应的Wife,方法二:

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="com.example.dao.HusbandDao">

<!--
resultMap:结果映射,一般在关联表查询中使用,将查询结果映射到实体类的属性中
type: 指定映射的对象类型
column: 字段名(如果使用as取别名,那就是别名)
property: 实体类中的属性名
association: 表明映射结果为一个
javaType: 指定映射的对象类型
-->

<select id="queryHusbandInfoAndWifeById" resultMap="queryHusbandInfoAndWifeByIdMap">
select * FROM husband h LEFT JOIN wife w ON h.hid = w.hid WHERE h.hid = #{hid}
</select>
<resultMap id="queryHusbandInfoAndWifeByIdMap" type="Husband">
<id column="hid" property="hid" />
<result column="hname" property="hname" />

<association property="wife" javaType="Wife">
<id column="wid" property="wid" />
<result column="wname" property="wname" />
<result column="hid" property="hid" />
</association>

</resultMap>

</mapper>

方法一和方法二的区别是,方法一需要查询两次,第一次查询的当前表,然后再根据关联字段去查询关联表。方法二是查询一次,通过左外连接把所有需要的数据一次性查出。

方法一是Mybatis提供的方式,这样做的主要是为了开启延迟加载后,当我们不需要使用关联对象时,是不会去运行第二个查询的。

方法二就比较粗暴了,无论你用不用关联对象都会将关联对象查询出来,当然写原生SQL的话,我也会这样干。

通过Husband的hid查询其所对应的Son,这是一个一对多查询:

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
<?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.example.dao.HusbandDao">

<!--
collection: 表明映射结果为多个
ofType: 返回值类型
-->
<select id="queryHusbandInfoAndSonById" resultMap="queryHusbandInfoAndSonByIdMap">
SELECT * FROM husband WHERE hid = #{hid}
</select>
<resultMap id="queryHusbandInfoAndSonByIdMap" type="Husband">
<id column="hid" property="hid" />
<result column="hname" property="hname" />

<collection property="sons" column="hid" ofType="Son" select="querySonByHid">
<id column="sid" property="sid" />
<result column="sname" property="sname" />
<result column="hid" property="hid" />
</collection>
</resultMap>
<select id="querySonByHid" resultType="Son">
SELECT * FROM son WHERE hid = #{hid}
</select>

</mapper>

这里说明一下一对一,多对一查询和一对多查询的区别,首先前者在进行非数据库字段属性映射时是使用association标签且使用javaType来表明返回值类型,后者在进行非数据库字段属性映射时是使用collection标签且使用ofType来表明返回值类型。

批量插入

批量插入的应用场景有很多,像通过前端导入一张excel,csv入库货品清单然后后台处理成指定格式后批量插入到数据库,或者从一张表查询一批数据后插入到另外一张表中等等。原生SQL可以直接使用navicat的导入功能,或者LOAD DATA LOCAL INFILE 命令。Python的Pymysql也可以使用executemany来进行高效率的批量插入。我这里记录一下使用Mybatis是如何进行批量插入的。

效率比较高的方式有两种,一种是Mybatis foreach方式,一种是Mybatis batch方式,两种方式的设置稍有不同。

通过Mybatis foreach对Husband进行批量插入,只需要在xml中设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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">

<!--
说明一下foreach的几个参数:
collection:指明需要遍历的参数类型,有list,array,map,对应的是xml对应接口方法的参数类型
open:整个语句用什么符号开始,如(
close:整个语句用什么符号结束, 如)
separator: 每条语句之间的分隔符, 如,
item: 被遍历出的单个对象
-->
<mapper namespace="com.example.dao.HusbandDao">

<insert id="addManyHusband">
INSERT INTO husband VALUES
<foreach collection="list" separator="," item="item">
(#{item.hid}, #{item.hname})
</foreach>
</insert>

</mapper>

通过Mybatis batch对Husband进行批量插入,这种方式后续会单独写一篇blog来记录一下其原理以及效率对比,这里就简短的描述一下其实现方式。Mybatis batch需要在openSession方法中设置ExecutorType.BATCH参数,默认为ExecutorType.SIMPLE,具体的设置方式如下:

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
// 这是单元测试中的写法,需要重新设置openSession中的参数
public class TestDemo {
private SqlSession sqlSession;
private HusbandDao husbandDao;

@Before
public void init() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); // 默认值的话这个方法不需要传参
husbandDao = sqlSession.getMapper(HusbandDao.class);
}

@After
public void close(){
sqlSession.commit();
sqlSession.close();
}

@Test
public void addManyHusbandMethodTwo(){
List<Husband> list = new ArrayList<>();
list.add(new Husband().setHid(1005).setHname("john"));
list.add(new Husband().setHid(1005).setHname("john"));
list.add(new Husband().setHid(1005).setHname("john"));
list.add(new Husband().setHid(1005).setHname("john"));
list.add(new Husband().setHid(1005).setHname("john"));
int effectRows = 0;
for (Husband h : list){
// 这里不去接收返回值,因为使用batch方式,返回值为-2147482646,后续记录原因
husbandDao.addManyHusbandMethodTwo(h);
sqlSession.clearCache(); // 清理缓存,防止插入过多内存溢出
effectRows += 1;

}
System.out.println(effectRows); // 打印插入行数
}
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">

<!--这是对应的XML-->
<mapper namespace="com.example.dao.HusbandDao">

<insert id="addManyHusbandMethodTwo">
INSERT INTO husband values (#{hid}, #{hname})
</insert>

</mapper>