Mybatis2

mybatis是一个优秀的开源框架,半自动ORM映射,能够适配各种业务需求

mybatis路线图(下)

  1. MyBatis的多表联合查询(了解一对一,熟练一对多(理解延迟加载),熟练多对多)
  2. MyBatis的动态SQL(嵌套的语句的几种方式,按需使用)
  3. MyBatis中#{}${}之间的区别
  4. MyBatis的延迟加载(什么是延迟加载,好处,场景)
  5. MyBatis的缓存机制(缓存机制策略,一级缓存和二级缓存的不同,二级缓存的实现)
  6. MyBatis逆向工程(了解逆向工程,思考不方便的地方)

MyBatis的多表联合查询

1对1的说明:在级联关系中OneToOne是比较不太频繁出现的一种情况(在数据库设计上面可以考虑共用主键关系,或者利用一对多进行变种实现,利用外键可设置唯一约束UNIQUE约束),我们应该专注sql语句,追求高度自由化,虽然mybatis提供一对一的关联设置,但是我们还是利用一对多的方式进行变种实现

引用:级联不是必须的,级联的好处是获取关联数据十分便捷,但是级联过多会增加系统的复杂度,同时降低系统的性能,此增彼减,所以当级联的层级超过3层时,就不要考虑使用级联了,因为这样会造成多个对象的关联,导致系统的耦合,复杂和难以维护.在现实的使用过程中,要根据实际情况判断是否需要使用级联.

1对m的实现方式与m对1实现方式:(场景:1个用户有多个贴子,多个帖子的著作人)

  • 表和实体关系说明:user表和post表为1:M的关系

  • 实体代码如下:自行get和set

1
2
3
4
5
6
7
8
9
public class User {	
private int uid;
private String uname;
private List<Post> posts;
}
public class Post {
private int pid;
private int pname;
}
  • 接口和mapper映射内容操作如下
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
/**
* 进行1对多的操作
* @author Yun
*
*/
public interface UserDao {
/**
* 获取用户的信息和所发的帖子
* @return
*/
List<User> getUsers();
}
//-----------对应的动作实现
<mapper namespace="com.wwj.dao.UserDao">
<!-- 定义一对多的resultmap -->
<resultMap type="com.wwj.model.User" id="users">
<id property="uid" column="uid" />
<result column="uname" property="uname" />
<collection property="posts" ofType="com.wwj.model.Post">
<id property="pid" column="pid" />
<result column="pname" property="pname" />
</collection>
</resultMap>
//----------数据库操作
<select id="getUsers" resultMap="users">
select u.*,p.*
from user u,post p
where u.uid = p.uid
</select>

接下来是多对一的实现也就是在站在帖子这一边,展现帖子的时候希望看到著作人是谁(注意:实体会发生一点小的变动,在post属性中添加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
public interface PostDao {

/**
* 获取所有帖子信息
* @return
*/
List<Post> getPosts();
}
//-------动作的实现映射
<mapper namespace="com.wwj.dao.PostDao">
<!-- 定义一对多的resultmap -->
<resultMap type="com.wwj.model.Post" id="posts">
<id property="pid" column="pid" />
<result column="pname" property="pname" />
<association property="user" javaType="com.wwj.model.User">
<id property="uid" column="uid" />
<result column="uname" property="uname" />
</association>
</resultMap>

<select id="getPosts" resultMap="posts">
select u.*,p.*
from user u,post p
where u.uid = p.uid
</select>
</mapper>

下面是多对多上面的操作场景(一个用户可以有多个兴趣,一个兴趣可能有多个人选择)

构建二个实体分别是animal和interest和第三方表(animal_interest)

  1. 创建一个第三方表的映射接口(animal_interestDao)和映射的实现(animal_interestMapper.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
37
38
39
40
    /**
* 根据兴趣id查看有多少用户选择
* @param iid
* @return
*/
List<Animal> getAnimalByIid(int iid);
/**
* 根据用户id查看有当前用户有哪些兴趣
* @param aid
* @return
*/
List<Interest> getInterestByAid(int aid);
//------对应的映射实现
<mapper namespace="com.wwj.dao.Animal_InterestDao">
<resultMap type="com.wwj.model.Animal" id="animals">
<id property="aid" column="aid" />
<result column="aname" property="aname" />
</resultMap>

<resultMap type="com.wwj.model.Interest" id="interests">
<id property="iid" column="iid" />
<result column="iname" property="iname" />
</resultMap>

<select id="getAnimalByIid" parameterType="int" resultMap="animals">
select
a.*,ai.iid
from animal a,animal_interest ai
where a.aid = ai.aid
and ai.iid = #{iid}
</select>

<select id="getInterestByAid" parameterType="int" resultMap="interests">
select
i.*,ai.aid
from interest i,animal_interest ai
where i.iid = ai.iid
and ai.aid = #{aid}
</select>
</mapper>
  1. 分别构建AnimalDao和InterestDao以及映射文件
  2. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
     //获取用户信息
    List<Animal> getAnimals();
    //-------
    <mapper namespace="com.wwj.dao.AnimalDao">
    <!-- 定义一对多的resultmap -->
    <resultMap type="com.wwj.model.Animal" id="animals1">
    <id property="aid" column="aid" />
    <result column="uname" property="uname" />
    <collection property="interests" column="aid" select="com.wwj.dao.Animal_InterestDao.getInterestByAid">
    </collection>
    </resultMap>

    <select id="getAnimals" resultMap="animals1" >
    select a.*
    from animal a
    </select>
    </mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public interface InterestDao {
/**
* 获取兴趣信息
* @return
*/
List<Interest> getInterests();
}
//----------------------------
<!-- 定义一对多的resultmap -->
<resultMap type="com.wwj.model.Interest" id="interests1">
<id property="iid" column="iid" />
<result column="uname" property="uname" />
<collection property="animals" column="iid" select="com.wwj.dao.Animal_InterestDao.getAnimalByIid">
</collection>
</resultMap>
<select id="getInterests" resultMap="interests1" >
select i.*
from interest i
</select>
</mapper>

MyBatis的动态SQL

模拟一张用户表进行说明,以及代码说明

1
2
3
4
5
6
7
8
9
10
11
12
public interface TestUserDao {
/**
* 依次为if/whereif/set/(whenchoose)/foreach
* @param msg
* @return
*/
TestUser getUser(Map msg);
TestUser getUserUseWhere(Map msg);
TestUser updateUserById(Map msg);
TestUser selectUserByChoose(Map msg);
List<TestUser> selectUserByListId(List ids);
}
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
<?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.wwj.dao.UserDao">

<resultMap type="com.wwj.model.TestUser" id="tuser">
<id property="tid" column="tid" />
<result column="tname" property="tname" />
<result column="tage" property="tage" />
</resultMap>

<select id="getUser" resultMap="tuser">
select * from testuser
where
<if test="tname != null">
tname=#{tname}
</if>
<if test="tage != null">
and tage=#{tage}
</if>
</select>

<!-- where”标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’.此外,如果标签首次返回的内容是以AND或OR开头的,则它会剔除掉。 -->
<select id="getUserUseWhere" resultMap="tuser">
select * from testuser
<where>
<if test="tname != null">
tname=#{tname}
</if>
<if test="tage != null">
and tage=#{tage}
</if>
</where>

<!-- (不常用)trim标记是一个格式化的标记,可以完成set或者是where标记的功能
prefix:前缀      
prefixoverride:去掉第一个
suffix:后缀  
suffixoverride:去掉最后一个
<trim prefix="where" prefixOverrides="and | or">
<if test="username != null">
and username=#{username}
</if>
<if test="sex != null">
and sex=#{sex}
</if>
</trim> -->
</select>
<update id="updateUserById" parameterType="java.util.Map">
update testuser
<set>
<if test="tname != null and tname != ''">
tname = #{tname},
</if>
<if test="tage != null and tage != ''">
tage = #{tage}
</if>
</set>

where tid=#{tid}
</update>
<select id="selectUserByChoose" resultMap="tuser" parameterType="java.util.Map">
select * from testuser
<where>
<choose>
<when test="tid !='' and tid != null">
tid=#{tid}
</when>
<when test="tname !='' and tname != null">
and tname=#{tname}
</when>
<otherwise>
and tage=#{tage}
</otherwise>
</choose>
</where>
</select>

<select id="selectUserByListId" resultMap="tuser" parameterType="java.util.List">
select * from testuser
<where>
<!--
collection:指定输入对象中的集合属性
item:每次遍历生成的对象
open:开始遍历时的拼接字符串
close:结束时拼接的字符串
separator:遍历对象之间需要拼接的字符串
select * from user where 1=1 and (id=1 or id=2 or id=3)
-->
<foreach collection="list" item="tid" open="and (" close=")" separator="or">
tid=#{tid}
</foreach>
</where>
</select>
</mapper>

MyBatis中#{}和${}区别

简单的说#{}是采用占位符的方式,而$是采用是字符串拼接的方式
字符串拼接的方式就一定会存在sql注入的问题


MyBatis的延迟加载

(延迟加载===按需加载)

  1. 比如刚才的一个用户有多个兴趣,现在需要用户信息的时候,编写sql语句的时候同时也把暂时不需要看用户的兴趣的数据加载出来,在数据量大的情况就肯定有瓶颈。
  2. 所以我们可以参照多对多示例中进行设置延迟加载,来观察sql语句的发出
1
2
3
4
5
6
7
//代码示例如下: 我仅仅需要用户信息,但是同样的也把其它非相关的信息加载出来了
List<Animal> as =session.selectList("getAnimals");
System.out.println(as.get(0).getAname());
//---------
DEBUG [main] - ==> Preparing: select a.* from animal a
DEBUG [main] - ==> Parameters:
DEBUG [main] - ====> Preparing: select i.*,ai.aid from interest i,animal_interest ai where i.iid = ai.iid and ai.aid = ?
  1. 配置延迟加载

全局配置文件中配置添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<settings>
<!-- 启用延迟加载特性,不配置默认关闭该特性-->
<setting name="lazyLoadingEnabled" value="true"></setting>
<!-- 按需加载: false:使用关联属性,及时加载; true,加载对象,则加载所有属性-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
//------信息展示结果
DEBUG [main] - ==> Preparing: select a.* from animal a
DEBUG [main] - ==> Parameters:
DEBUG [main] - <== Total: 2
DEBUG [main] - ==> Preparing: select i.*,ai.aid from interest i,animal_interest ai where i.iid = ai.iid and ai.aid = ?
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <== Total: 3
唱歌

MyBatis的缓存机制

  1. 一级缓存(缓存不相互共享,在同一个事务中,如果存在同样的操作,中间不带增删改操作的话。那么不在进行二次IO读取操作)
1
2
3
4
5
6
7
8
9
10
		List<Animal>  as =session.selectList("getAnimals");
System.out.println(as.get(0).getAname());
List<Animal> ass =session.selectList("getAnimals");
System.out.println(as.get(0).getAname());
//------
DEBUG [main] - ==> Preparing: select a.* from animal a
DEBUG [main] - ==> Parameters:
DEBUG [main] - <== Total: 2
用户1
用户1
  1. 二级缓存(缓存共享)需要映射的接口对应的映射文件加入:
1
2
<mapper namespace="com.wwj.dao.AnimalDao">
<cache/>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
SqlSession session = build.openSession();
List<Animal> as =session.selectList("getAnimals");
System.out.println(as.get(0).getAname());
List<Animal> ass =session.selectList("getAnimals");
System.out.println(as.get(0).getAname());
// 提交事务
session.commit();
// 关闭 session
session.close();
System.out.println("---------------------------分割线利于观察");
//关闭了之后数据会放入缓存中
//测试二级缓存
SqlSession session1 = build.openSession();
List<Animal> asss =session1.selectList("getAnimals");
System.out.println(as.get(0).getAname());
// 提交事务
session1.commit();
// 关闭 session
session1.close();

1
2
3
4
5
6
7
8
9
10
11
//-----控制台结果
DEBUG [main] - <== Total: 2
用户1
DEBUG [main] - Cache Hit Ratio [com.wwj.dao.AnimalDao]: 0.0
用户1
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.Connection@8c03696]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.Connection@8c03696]
DEBUG [main] - Returned connection 146814614 to pool.
---------------------------分割线利于观察
DEBUG [main] - Cache Hit Ratio [com.wwj.dao.AnimalDao]: 0.3333333333333333
用户1

(缓存机制策略的补充)

  1. 默认mybatis映射语句文件中所有的select语句将会被缓存
    映射语句文件中所有的insert update delete 语句会刷新缓存
    缓存会使用(Least Flush Interval,LRU最近最少使用的)算法来收回
    根据时间表(如 no Flush Interval,没有刷新间隔),缓存不会以任何时间顺序来刷新
  2. eviction(收回策略===更新策略)
    LRU 最近最少使用的,移除最长时间不被使用的对象,这是默认值
    FIFO 先进先出,按对象进入缓存的顺序来移除它们
    SOFT 软引用,移除基于垃圾回收器状态和软引用规则的对象
    WEAK 弱引用,更积极的移除基于垃圾收集器状态和弱引用规则的对象
  3. 缓存存在数据可能出现脏读的现象(操作同一数据)缓存数据尽量用在变更不频繁的数据上面
    影响缓存的三个因素(缓存更新策略,缓存最大数据量,命中率)

MyBatis逆向工程

  1. 逆向工程我们做简单的展示
  2. 逆向工程的含义在于根据数据库的表的结构以面向对象的方式自动的帮助我们生成对应的实体类
  3. 逆向为什么不太常用,因为一旦数据库的结构和关联发生变化,那么实际开发过程中就需要自己手动调整对应的实体类,这个可以说是一个非常浩瀚的工程,无论从人力和无力成本看来都得不偿失
  4. 以前我们可以说小的项目,用逆向比较方便,不如我们把逆向看成是不可取的,mybatis本身也是追求语句的自由化。所以逆向作为了解即可.
  • 引入对应的jar包
  • 构建逆向配置xml文件generatorConfig.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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?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">
<!-- targetRuntime=“MyBatis3“可以生成带条件的增删改查,targetRuntime=“MyBatis3Simple“可以生成基本的增删改查 -->
<generatorConfiguration>
<context id="testTables" targetRuntime="MyBatis3Simple">

<commentGenerator>
<!-- 是否去除自动生成的注释 true:是 : false:否 -->
<property name="suppressAllComments" value="true" />
</commentGenerator>

<!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis" userId="root"
password="root">
</jdbcConnection>

<!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL 和
NUMERIC 类型解析为java.math.BigDecimal -->
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>


<!-- targetProject:生成PO类的位置 -->
<javaModelGenerator targetPackage="com.wwj.model1"
targetProject="./src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
<!-- 从数据库返回的值被清理前后的空格 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>

<!-- targetProject:mapper映射文件生成的位置 -->
<sqlMapGenerator targetPackage="com.wwj.dao1"
targetProject="./src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>


<!-- targetPackage:mapper接口生成的位置 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.wwj.dao1"
targetProject="./src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</javaClientGenerator>


<!-- 指定数据库表 -->
<table schema="mybatis" tableName="person" domainObjectName="personG"></table>

</context>
</generatorConfiguration>
  • 构建生成main函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class TestG {
public void generator() throws Exception{

List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
//指定 逆向工程配置文件
File configFile = new File("generatorConfig.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,
callback, warnings);
myBatisGenerator.generate(null);

}
public static void main(String[] args) throws Exception {
try {
TestG generatorSqlmap = new TestG();
generatorSqlmap.generator();
} catch (Exception e) {
e.printStackTrace();
}

}