Mybatis框架介绍与基本使用笔记

注意:一般的一个Maven工程首先注入的依赖包含数据库驱动依赖,日志依赖,测试依赖

domain中的实体类实现serizlizable接口序列化的原因:

最重要的两个原因是:

  1、将对象的状态保存在存储媒体中以便可以在以后重新创建出完全相同的副本;

  2、按值将对象从一个应用程序域发送至另一个应用程序域。

  实现serializable接口的作用是就是可以把对象存到字节流,然后可以恢复。所以你想如果你的对象没实现序列化怎么才能进行网络传输呢,要网络传输就得转为字节流,所以在分布式应用中,你就得实现序列化,如果你不需要分布式应用,那就没那个必要实现序列.

namespace:名称空间;写接口的全类名;相当于告诉Mybatis这个配置文件是实现哪个接口的;

一、Mybatis相关配置

[1] mybatis配置文件中config与mapper的约束

自己预设定的配置文件:

1.1 config

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 configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd"> <!-- 引入Mybatis的配置声明dtd文件 -->
<!-- mybatis的主配置文件 -->
<configuration>
<!-- 配置环境 若想让environments环境起作用,下列的标签中的配置都需要起作用 -->
<environments default="mysql">
<!-- 配置mysql(default的值)环境 id值等于default的值 -->
<environment id="mysql">
<!-- 配置事务的类型 -->
<transactionManager type="JDBC"/>
<!-- 配置数据源(连接池 -\- druid、c3p0..) -->
<dataSource type="POOLED">
<!-- 配置数据库连接的基本信息 -->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://url:3306/mybatis?characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- 使用的是注解 指定映射配置文件的位置(使用注解时,不能留下配置文件的方式,否则会冲突),映射配置文件指的是每个dao对立的配置文件-->
<!-- <mapper class="cn.lizhi.mybatis_01.dao.UserDao"/> -->
<!-- 该包下所有的dao接口都可以使用 -->
<!-- <package name="cn.lizhi.mybatis_01.dao"/>-->
<!-- 配置文件的指定方式 -->
<mapper resource="cn/lizhi/mybatis_01/dao/UserDao.xml"/>
</mappers>
</configuration>

1.1.1 通过properties标签引入外部配置信息(动态配置)

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
<?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"> <!-- 引入Mybatis的配置声明dtd文件 -->
<!-- mybatis的主配置文件 -->
<configuration>
<!-- 配置properties
可以在标签内部配置连接数据库的信息。也可以通过属性引用外部配置文件信息
resource属性:
用于指定配置文件的位置,是按照类路径的写法来写,并且必须存在与类路径下
url属性:
是要求按照url的写法来写地址
写法:
http://localhost:8080/servlet/demoServlet
协议 主机 端口 URI
URI:统一资源标识符。应用中可以唯一定位一个资源。
-->
<!-- 方式1.jdbcConfig.properties 是存放在类(resources)路径的根路径下 -->
<properties resource="jdbcConfig.properties"/>

<!-- 方式2. 这种是在标签内部配置连接数据库信息
<properties>
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://url:3306/mybatis?characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</properties>
-->

<!-- 配置环境 若想让environments环境起作用,下列的标签中的配置都需要起作用 -->
<environments default="mysql">
<!-- 配置mysql(default的值)环境 id值等于default的值 -->
<environment id="mysql">
<!-- 配置事务的类型 -->
<transactionManager type="JDBC"/>
<!-- 配置数据源(连接池 -\- druid、c3p0..) -->
<dataSource type="POOLED">
<!-- 配置数据库连接的基本信息 -->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="cn/lizhi/mybatis_01/dao/UserDao.xml"/>
</mappers>
</configuration>

jdbcConfig.properties

1
2
3
4
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://url:3306/mybatis?characterEncoding=utf8
jdbc.username=root
jdbc.password=root

1.1.2

2.1 Mapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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.lizhi.dao.IUserDao">
<!--配置查询所有,resultType的作用就是返回封装的位置,如果你要是不写的话,最后mybatis是不知道你到底要封装到哪里,会出现错误,我这个是User表,查询的也是这个,最后返回的结果就封装在User类中 id要写对应dao中的方法名称-->
<select id="findAll" resultType="com.lizhi.domain.User">
SELECT *FROM user
</select>
<!--注意在resources中,目录是一级结构,要一个一个创建,而cn.lizhi.mybatis.dao是一个目录。而包是三级结构-->
</mapper>

<!-- 使用时直接复制以下的即可 -->

<?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="">
<select id="" resultType="">
SELECT *FROM user;
</select>
</mapper>

通过实例 – 由Mybatis创建实现Dao的接口,进行数据库的查询时,在mapper中只有id是无法定位到具体数据库访问的方法的,所以要加上namespace,限定住工作空间(即限定住全类名,一个类中,方法名是唯一的)。可以简单理解为namespace是定位到具体的一个dao接口,id是定位到当前这个dao下的具体方法。

3.1 log4j的日志配置文件

log4j配置文件放入resource资源目录下,记录日志,对日志的操作。

二、Mybatis的注意事项

Mybaits入门使用

  • 两个配置文件的编写
    • config(全局环境Mybatis配置文件)
    • mapper(映射配置文件) – 要与dao的目录结构相同
      • select中的,resultType的作用就是返回封装的位置,如果你要是不写的话,最后mybatis是不知道你到底要封装到哪里,会出现错误,我这个是User表,查询的也是这个,最后返回的结果就封装在User类中 id要写对应dao中的方法名称。

三、快速入门

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MybatisTest {
public static void main(String[] args) throws IOException {
// 1. 读取配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
// 2.创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
// 3.使用工厂生产SqlSession对象
SqlSession session = factory.openSession();
// 4.使用SqlSession创建Dao接口的代理对象
UserDao userDao = session.getMapper(UserDao.class);
// 5.使用代理对象执行方法
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}
session.close();
in.close();
}
}

其中Resources(ibatis)类默认加载resource资源路径下的配置文件。

四、路径问题与代理dao的方式解析

  • 绝对路径:d:/xxx/xxx/xml –> 采用:类加载器,它只能读取类路径的配置文件
  • 相对路径:src/java/main/xxx.xml (在web项目下没有src目录文件) –> 使用ServletContext对象的getRealPath()获取web下真实运行的类的路径。
1
2
3
// 2.创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);

以上创建工厂使用了构建者模式。 — builder就是构建者。

构建者模式:把对象的创建细节隐藏,是使用者直接调用方法即可拿到对象。

1
2
// 3.使用工厂生产SqlSession对象
SqlSession session = factory.openSession();

通过factory生产SqlSession使用了工厂模式。优势:解耦(降低类之间的依赖关系)。没有用new,不需重新边编译,解决了类之间的依赖关系。通过工厂生产对象。

1
2
// 4.使用SqlSession创建Dao接口的代理对象
UserDao userDao = session.getMapper(UserDao.class);

创建Dao接口实现类使用了代理模式。优势:不修改源码的基础上对已有方法增强。

代理dao的方式解析

  1. 连接数据库信息 – 用于创建connection对象。
  2. 同时在config配置中的有了mapper就有了所需要映射的信息(位置)
  3. 映射到Mapper文件,即可查询到全类名,id(接口方法),sql语句。便能获取到PreparedStatement

对以上进行读取配置文件(用到解析XML的技术) – 此处用到的是dom4j解析xml的技术

继而:

  1. 根据配置文件的信息创建Connection对象

    注册驱动,获取连接

  2. 获取预处理对象PreparedStatement

    此时需要SQL语句 –> 从3中的sql语句中获取

  3. 执行查询

此节涉及源码,待我理解完了,再填上。

五、Mybatis的增删改查

Mapper配置文件中,增加配置信息,其中id为接口中的方法名.

如果在测试类中,自定义方法上@Before表示在测试方法前执行;@After表示在测试方法之后执行。

sqlSession.commit() – 手动提交事务。resultType的指定是在对数据库的结果进行封装时的返回值类型,告诉Mybatis应该封装到哪里的参数。parameterType指查询的时候查询参数类型

5.1 findAll() – 查找全部用户的信息

1
2
3
<select id="findAll" resultType="cn.lizhi.mybatis_01.domain.User">
SELECT *FROM user;
</select>

resultType为全类名,表示需要封装到的对象。

5.2 saveUser – 保存用户信息(插入用户)

1
2
3
<insert id="saveUser" parameterType="cn.lizhi.mybatis_01.domain.User">
insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday});
</insert>

parameterType为提供属性的domain中的对象全类名。values中是写{}是写domain对象中的属性名称。

如果要想获得当前保存的信息的id,其使用方法是:

select last_insert_id() – 获取最后一条插入语句的id

1
2
3
4
5
6
7
<insert id="saveUser" parameterType="cn.lizhi.mybatis_01.domain.User">
<!-- KeyPropery代表要返回的值名称,order:取值为AFTER代表插入后的行为 resultType代表返回值的类型 -->
<selectKey keyProperty='id' order='AFTER' resultType="java.lang.Integer">
select last_insert_id();
</selectKey>
insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday});
</insert>

5.3 updateUser – 更新用户

1
2
3
<update id="updateUser" parameterType="cn.lizhi.mybatis_01.domain.User">
update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id};
</update>

5.4 deleteUser – 删除用户

1
2
3
<delete id="deleteUser" parameterType="Integer">
delete from user where id=#{uid}; <!-- 这个id的占位符可以随便写 -->
</delete>

在删除方法中parameterType只指定id,没有像上面一样指定User对象,是因为,我们这个是只对表进行操作,没有经过User对象的取值或使用(即在delete中,指定了我所要删除的是哪张表,并指定了id,那么我就可以通过这两项信息去定位到我所要删除的那条记录)。

5.5 findById – 根据用户id查询一条记录

1
2
3
<select id="findById" parameterType="Integer" resultType="cn.lizhi.mybatis_01.domain.User">
SELECT *FROM user where id=#{uid};
</select>

5.6 findByName – 模糊查询

1
2
3
4
5
<select id="findById" parameterType="String" resultType="cn.lizhi.mybatis_01.domain.User">
SELECT *FROM user where username like #{name};
或者
SELECT *FROM user where username like '%${value}%'; <!-- value是固定写法 -->
</select>

在进行MyBatis时的模糊查询时,通配符需要需要设置在参数中,即和查询的参数组成字符串,而不是将通配符写在mapper配置文件中。推荐用第一种,预编译可防止SQL注入。

5.7 findTotal – 聚合函数的使用

1
2
3
<select id="findTotal" resultType="int">
SELECT count(id) FROM user;
</select>

六、MyBatis的参数深入

6.1 parameterType(查询参数的输入类型)

  1. Integer,String等简单类型

  2. OGNL表达式 Object Graphic Navigation Language

    通过对象的取值方法来获取数组,在写法上将get省去。

    例如获取用户的名称:

    ​ 类中的写法:user.getUsername();

    ​ OGNL表达式写法:user.username;

    Mybatis中能直接写username,而不用user.的原因是:

    ​ 因为在parameterType中已经提供了属性所属的类,所以此时不需要写对象名。例如:

    1
    2
    3
    <update id="updateUser" parameterType="c">
    update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id};
    </update>

    ​ 上面的参数都是直接写属性名称,而没有通过对象.属性的方式进行获取值username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id}

    这里的用处:当我们的查询条件被封装成一个对象时,就需要采用OGNL表达式。

    例如封装了一个对象QueryVo

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class QueryVo {

    private User user;

    public User getUser() {
    return user;
    }

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

    此时将QueryVo作为查询对象时,mapper配置文件应该写成:

    1
    2
    3
    <select id="findUserByVo" parameterType="cn.lizhi.mybatis_01.domain.QueryVo" resultType="cn.lizhi.mybatis_01.domain.User">
    select * from user where username like #{user.username}
    </select>

    {user.username}的解释,根据上面的解释,我们知道userQueryVo类的一个属性,我们可以直接获取属性,而usernameuser类的属性,所以可以继而通过对象.属性(继续对ONGL表达式的嵌套使用)的方式获取。

    以上这种适用于由多个对象组成的查询条件,实现对数据库的进行查询

  3. 传递pojo对象

    Mybatis使用ognl表达式解析对象字段的值,#{}或者${}括号中的值为pojo属性名称。

    其中对pojojavaBean两者的区别,参考链接:java对象 POJO和JavaBean的区别。可以简单的把pojo理解为我们定义的实体类。

  4. 传递pojo包装对象

    开发中通过pojo传递查询条件,查询条件时综合的查询条件,不仅包括用户查询条件还包含其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。Pojo类中包含pojo.

    例如上面的:根据用户名查询用户信息,查询条件放到QueryVouser属性中。

6.2 resultType(输出类型)

  1. 可以输出IntegerString等简单类型
  2. pojo对象
  3. pojo列表
  • 问题一

    在属性和Mysql数据库表中字段不统一时,会无法进行封装。

    注意:windows下的MySQL不区分大小写;Linux下的MySQL严格区分大小写。

    解决方式:

    • Mapper配置文件中的sql语句中对操作字段起别名。别名和pojo中的属性名称相同。

      例如: select id as userId,username as userName,address as userAddress,sex as UserSex from user;

      这种方式运行效率最高,因为在数据库层面进行了解决,速度更快。

    • 配置 查询结果的列名和实体类的属性名的对应关系

      1
      2
      3
      4
      5
      6
      7
      8
      9
      <resultMap id="userMap" type="cn.lizhi.mybatis_01.domain.User">
      <!-- 主键字段的对应 -->
      <id property="userId" column="id"></id>
      <!-- 非主键字段的对应 -->
      <result property="userName" column="username"></result>
      <result property="userAddress" column="address"></result>
      <result property="userSex" column="sex"></result>
      <result property="userBirthday" column="birthday"></result>
      </resultMap>

      id:唯一标志 – 随便填写。用于selectinsertdelete等配置的类型映射。

      type:查询的实体类,所对应的实体类是哪一个 – 全类名

      举例使用:

      1
      2
      3
      <select id="findAll" resultMap="userMap">
      select * from user;
      </select>

      即将以前的resultType替换成resultMap,值匹配resultMap配置中的id,这样就能够进行映射匹配。

      这种方式能够提升开发效率。

6.3 配置别名文件

  1. typeAliases配置别名,用于配置domain中类的别名 – 指定实体类别名
1
2
3
<typeAliases>
<typeAlias type="cn.lizhi.mybatis_01.domain.User" alias="user"></typeAlias>
</typeAliases>

typeAlias用于配置别名。type属性指定的是实体类全限定类名。alias属性指定别名,当指定了别名就不再区分大小写。

  1. package用于指定要配置别名的包,当指定后,该包下的实体类都会注册别名,并且类名就是别名,不再区分大小写。 – 指定实体类别名

    1
    2
    3
    <typeAliases>
    <package name="cn.lizhi.mybatis_01.domain"></package>
    </typeAliases>
  2. <mapper>
        <!-- package标签是用于指定dao接口所在的包,当指定了之后,就不需要再写mapper以及resource或者class -->
        <package name="cn.lizhi.mybatis_01.dao"></package>
    </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

    作用于接口,`package`标签是用于指定`dao`接口所在的包,当指定了之后,就不需要再写mapper以及`resource`或者`class`

    ## 七、Mybatis实现Dao层的开发

    ### 7.1 Dao实现类的使用

    由于是自己写实现类,所以就不需要代理对象对我们的方法进行增强。

    首先实现类的方法:

    ```java
    public class UserDaoImpl implements UserDao {

    private SqlSessionFactory factory;

    public UserDaoImpl(SqlSessionFactory factory) { // 保证我们的factory中有值,通过构造方法将配置文件读取
    this.factory = factory;
    }

    public List<User> findAll() {
    SqlSession session = factory.openSession();
    List<User> user = session.selectList("cn.lizhi.mybatis_01.dao.UserDao.findAll");// 配置文件中namespace+id
    session.close();
    return user;
    }
    }
    测试类:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Test
    public void findAllTest() throws IOException {
    InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    SqlSessionFactory factory = builder.build(is);
    UserDaoImpl userDao = new UserDaoImpl(factory);
    List<User> users = userDao.findAll();
    for (User user : users) {
    System.out.println(user);
    }
    is.close();
    }
    ### 7.2 session中各种操作数据库语句
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 保存方法
    session.insert("namespace+id",Object)
    // 更新操作
    session.update("namespace+id",Object)
    // 删除操作
    session.delete("namespace+id",Integer id)
    // 单条查询
    session.selectOne("namespace+id",Integer id)
    // 聚合函数
    session.selectOne("namespace+id")
    ### 7.3 源码分析
  • PreparedStatement对象的执行方法

    • execute:执行CRUD中的任意一种语句。它的返回值是一个boolean类型,表示是否有结果集。有结果集是true,没有结果集是false
    • executeUpdate:只能执行CUD语句,查询语句无法执行。他的返回值是影响数据库记录的记录数。
    • executeQuery:只能执行select语句,无法执行增删改。执行结果封装的结果集ResultSet对象。

以后再补上…

八、Mybatis连接池与事务相关

连接池介绍:可以减少我们获取连接所消耗的时间。用于存储连接的一个容器。

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

8.1 Mybatis连接池

配置位置:

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

  • type属性:

    • POOLED:采用传统的javax.sql.DataSource规范中的连接池,Mybatis中有针对规范的实现(每次从池中获取连接,连接完以后归还)

    • UNPOOLED:采用传统的获取连接的方式,虽然也实现Javax.sql.DataSource接口,但是并没有实现池的思想(每次创建一个连接来使用)

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

      注意:如果不是web或者mavenwar工程,是不能使用的。

      这里采用dbcp连接池。

Mybaits POOLED的连接池原理:

连接池源代码

mybatis_pooled的过程

8.2 事务

解决4个问题

  1. 什么是事务
  2. 事务的四大特性ACID
  3. 不考虑隔离性会产生的3个问题
  4. 解决办法:四种隔离级别

Mybatis底层实现还是借用JDBC

九、Mybatis动态sql

9.1 动态查询

动态查询(组合查询) – 查询条件不确定有没有的情况.

查询条件:可以根据usernamegenderage等多条件组合查询

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
30
31
<!-- 根据条件进行查询 -->
<select id="方法名" resultType="返回值类型" parameterType="查询的参数类型">
select * from user where 1=1
<if test="username!=null(条件1)"> <!-- username是对应的实体类属性名(判断是否提供了这个查询条件的参数值) -->
and username = #{username} <!-- 左边是数据库column字段名称,右边是实体类属性名称 -->
</if>
<if test="(条件2)">
and sex = #{sex}
</if>
<if test="(条件3)">
and age = #{age} <!-- 注意属性名和字段名对应问题 -->
</if>
...
</select>

<!-- where 标签可以省略 初始条件的 where -->
<select id="方法名" resultType="返回值类型" parameterType="查询的参数类型">
select * from user
<where>
<if test="(条件1)">
and username = #{username}
</if>
<if test="(条件2)">
and sex = #{sex}
</if>
<if test="(条件3)">
and age = #{age} <!-- 注意属性名和字段名对应问题 -->
</if>
...
</where>
</select>

根据可能提供的条件,进行组合查询(这些条件可能存在也可能不存在),例如:

主查询代码:

1
2
3
4
5
6
7
8
9
@Test
public void findUserByCondition() throws IOException {
User user = new User();
user.setUsername("Tom"); // 查询出所有叫Tom的记录,提供的查询条件只有"Tom"
List<User> users = userDao.findUserByCondition(user);
for (User u : users) {
System.out.println(u);
}
}

对应表的配置文件查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<select id="findUserByCondition" parameterType="cn.lizhi.mybatis_01.domain.User" resultType="cn.lizhi.mybatis_01.domain.User">
select * from user
<where>
<if test="username != null">
and username=#{username}
</if>
<if test="sex != null">
and sex=#{sex}
</if>
<if test="address != null">
and address=#{address}
</if>
<if test="address != null">
and address=#{address}
</if>
<if test="birthday != null">
and birthday=#{birthday}
</if>
</where>
</select>

9.2 子查询

子查询:根据被封装对象(例如queryvo)的id集合,查询用户信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- 根据queryvo中的Id集合实现查询用户列表 -->
<select id="findUserInIds" resultMap="userMap" parameterType="queryvo">
select * from user
<where>
<if test="ids !=null and ids.size()>0">
<foreach collection(代表一个集合)="ids" open="and id in (" close=")" item="uid(集合中每个元素的代称)" separator(每一项的分隔符)=","> <!-- 查询出id在集合中的全部条目 -->
#{uid}
</foreach>
</if>
</where>
</select>
<!-- 属性解释 -->
<foreach>:标签用于遍历集合,它的属性:
collection:代表要遍历的集合元素,注意编写时不要写#{}
open:代表语句的开始部分
close:代表结束部分
item:代表遍历集合的每个元素,生成的变量名
sperator:代表分隔符

十、Mybatis中的多表查询

示例:用户和账户

  • 一个用户可以有多个账户(一对多)
  • 一个账户只能属于一个用户 (多个账户也可以属于同一个用户;一对一或者多对一)

步骤:

  1. 建立两张表:用户表、账户表

    让用户表和账户表之间具备一对多的关系:需要使用外键在账户表中添加

  2. 建立两个实体类:用户实体类和账户实体类

    让用户和账户的实体类能体现出一对多的关系

  3. 建立两个配置文件

    • 用户的配置文件
    • 账户的配置文件

    注意:一张数据库表对应一个实体类且对应一个配置文件,并在相应的配置文件中进行配置操作。

  4. 实现配置

    • 当我们查询用户时,可以同时得到用户下所包含的账户信息
    • 当我们查询账户时,可以同时得到账户的所属用户信息
  5. 一的一方是主表;多的一方是主表。

10.1 Mybatis一对一的查询

查询所有账户同时包含用户名和地址信息

方式一:新建子类对象accountUser继承account

accountUser实体类中包含需要展示的User实体类中的信息,例如需要展示usernameaddress。那么accountUser定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class AccountUser extends Account{
private String username;
private String address;

/*
省略了getter与setter方法
*/

@Override
public String toString() {
return super.toString()+"AccountUser{" +
"username='" + username + '\'' +
", address='" + address + '\'' +
'}';
}
}

mapper中配置的查询信息

1
2
3
4
<!-- 新建子类对象accountUsr继承account -->
<select id="findAllAccount" resultType="accountUser">
select 所要查询的字段 from 表1,表2 where 表1.字段 = 表2.字段 <!-- 组合查询 -->
</select>

方式二:定义封装accountuserresultMap

例如在从表domainAccount的实体类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Account implements Serializable {

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

private User user; // 这里从表中包含了主表实体的对象引用

/*
省略了getter与setter方法
*/

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

Mybatis多对一一对一中,从表的实体类和主表的实体类关系:从表实体应该包含主表实体的对象应用

定义封装accountuserresultMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- 定义封装account和user的resultMap -->
<resultMap id="accountUserMap" type="account"> <!-- type表明从表封装的对象 -->
<!-- 先写从表的信息 配置account信息的封装-->
<!-- 主键信息 -->
<id property="id" column="aid"></id>
<!-- 非主键信息 -->
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<!-- 主表信息 一对一的关系映射:配置封装user的内容 -->
<!--多对一的关系, property: 指的是属性的值(account中),column:指通过哪一个字段进行获取属性值,这里选择uid(外键), javaType:指的是属性的类型 -->
<association property="user" column="uid" javaType="user"> <!-- javaType表明从表中主表实体需要封装的对象(因为配置了别名,所以这里没有写全类名) -->
<!-- 主键信息 -->
<id property='id' column='id'></id>
<!-- 非主键字段 -->
<result column="username" property="username"></result>
<result column="address" property="address"></result>
<result column="sex" property="sex"></result>
<result column="birthday" property="birthday"></result>
</association>
</resultMap>

查询所有,一对一的查询(在查询账户时,也会查询出用户信息)

1
2
3
<select id="findAll" resultMap="accountUserMap">
select 所要查询的字段 from 表1,表2 where 表1.字段 = 表2.字段 <!-- 组合查询 -->
</select>

10.2 Mybatis一对多的查询

一对多映射:主表实体应该包含从表实体的集合引用。

DomainUser加入从表实体的集合引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
}
/*
其余属性的getter与setter方法省略,以及toString()方法的省略
*/

对应的mapper配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- 定义封装User的resultMap -->
<resultMap id="userAccountMap" type="user"> <!-- type表明主表封装的对象 -->
<!-- 主键信息 -->
<id property='id' column='id'></id>
<!-- 非主键字段 -->
<result column="username" property="username"></result>
<result column="address" property="address"></result>
<result column="sex" property="sex"></result>
<result column="birthday" property="birthday"></result>
<!-- 配置user对象中accounts集合的映射 -->
<collection property="accounts" ofType="account"> <!-- ofType写集合中元素的对象类型(因为配置了别名,所以这里没有写全类名) -->
<!-- 主键信息 以下property是java domain的属性,column是数据库列名(可以用别名代替) -->
<id property="id" column="aid"></id>
<!-- 非主键信息 -->
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
</collection>
</resultMap>
1
2
3
<select id="findAll" resultMap="userAccountMap">
数据库中的查询语句 <!-- 组合查询 -->
</select>

resultMap中,需要注意两个id中是否存在column重名的情况,如果相同将其中一个进行修改(查询数据库时起别名)。查询时,建议将所有字段内容都查询出,便于对对象的封装(对象的封装的内容就是来源于查询的内容返回的内容,propertycolumn对应)。

10.3 Mybatis 多对多查询

实例:用户和角色

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

步骤:

  1. 建立两张表:用户表、角色表

    让用户表和角色表之间具备多对多的关系:需要使用中间表,中间表包含各自的主键,在中间表中是外键。

  2. 建立两个实体类:用户实体类和角色实体类

    让用户和角色的实体类能体现出多对多的关系

    各自包含对方一个集合引用

  3. 建立两个配置文件

    • 用户的配置文件
    • 角色的配置文件
  4. 实现配置

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

两个实体类中各自加入多对多的实体关系映射。

第一种:查询所有角色,同时获取角色的所赋予的用户。即:在查询角色时,同时获取到它的全部用户信息。

以中间表作为连接的媒介。确定出查询角色时,同时获取角色下的全部用户信息(可以简单的看成一对多的查询关系)。这里角色表作为主表(用左外连接,保存主表的全部信息)

Mapper配置同一对多。

第二种:查询所有的用户,同时获取用户的所拥有的角色。

  • 思路同第一种

10.4 JNDI补充

模仿windows的注册表。

插图

  1. 创建Mavenwar工程。

  2. webapp下创建META-INF,将context.xml方入此目录下。

  3. 替换原有的SqlMapConfig

    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
    <?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>
    <typeAliases>
    <package name="com.itheima.domain"></package>
    </typeAliases>
    <!-- 配置mybatis的环境 -->
    <environments default="mysql">
    <!-- 配置mysql的环境 -->
    <environment id="mysql">
    <!-- 配置事务控制的方式 -->
    <transactionManager type="JDBC"></transactionManager>
    <!-- 配置连接数据库的必备信息 type属性表示是否使用数据源(连接池)-->
    <dataSource type="JNDI">
    <property name="data_source" value="java:comp/env/jdbc/eesy_mybatis"/>
    </dataSource>
    </environment>
    </environments>

    <!-- 指定mapper配置文件的位置 -->
    <mappers>
    <mapper resource="com/itheima/dao/IUserDao.xml"/>
    </mappers>
    </configuration>
  4. 将之间的测试方法写在jsp的java代码块中。

    原因是有tomcat服务器进行,将.jsp翻译成.java再进行编译,运行字节码文件,进而使用tomcat服务器内部的资源连接池再通过Mybatis访问数据库。

十一、Mybatis缓存相关

11.1 Mybatis中的延迟加载

问题:

在一对多中,当我们有一个用户,它有100个账户。(用户对象-user;accounts(集合,size=100))

问:在查询用户的时候,要不要把关联的账户查询出来?

答:在查询用户时,用户下的账户信息应该是,什么时候使用,什么时候查询的。

问:在查询账户的时候,要不要把关联的用户查询出来?

答:在查询用户时,账户的所属用户信息应该是随着账户查询时一起查询出来。

延迟加载:在真正使用数据时才发起查询,不用的时候不查询。按需加载(懒加载)。

立即加载:无论是否使用,只要一调用方法,马上发起查询。

在对应的四种表关系中:一对多,多对一,一对一,多对多

  • 一对多,多对多:通常情况下都采用延迟加载。
  • 多对一,一对一:通常情况下采用立即加载。

11.1.1 一对一(association)

延迟加载:

全局配置文件需设置

  • lazyLoadingEnabled:true
  • aggressiveLazyLoading:false
1
2
3
4
5
<settings>
<!-- Mybatis全局延迟加载的开关,true表示打开-->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>

mapper的配置文件:

1
2
3
4
5
6
7
8
<!-- 一对一的查询 -->
<resultMap id="accountUserMap" type="account">
<id property="id" column="id"></id>
<result proerty="uid" column="uid"></result>
<result property="money" column="money"></result>
<association property="user" column="uid" javaType="user" select="cn.lizhi.mybatis_01.dao.UserDao.findById">
</association>
</resultMap>

select属性:查询用户的唯一标识,这个例子中是关联信息的主键id。例如这里account表中的uid对应着user表中的id。所以我们要使得uidid进行匹配,故在user中的唯一标识就是id。反之,如果我们通过查询user表同时将其关联的account表的信息一并查出,此时user表中的id对应着account表中的uid,那么uid便是所要查询的关联表的唯一标识。

这里对唯一标识的理解是两个表之间相关联的字段。

column属性:查询用户是根据id查询,而id对应的是account表中的uid,所需要的参数的值(外键)- 根据当前表中的uid去查询user表的记录(对应到user表中的主键)。即,拿着当前表的uid(外键),去user表中匹配主键,查询出记录。

cn.lizhi.mybatis_01.dao.UserDao.findById:

1
2
3
<select id="findById" parameterType="Integer" resultType="cn.lizhi.mybatis_01.domain.User">
select * from user where id=#{uid};
</select>

cn.lizhi.mybatis_01.dao.AccountDao.findAllByCount

1
2
3
<select id="findAllByCount" resultMap="accountUserMap">
select * from account
</select>

以上可以实现懒加载。

11.1.2 一对多(Collection)

使用方法同上:

1
2
3
4
5
6
7
8
9
10
11
<!-- 一对多的查询 -->
<resultMap id="userAccountMap" type="user">
<id property='id' column='id'></id>
<result column="username" property="username"></result>
<result column="address" property="address"></result>
<result column="sex" property="sex"></result>
<result column="birthday" property="birthday"></result>
<collection property="accounts" ofType="account" select="cn.lizhi.mybatis_01.dao.AccountDao.findAccountByUid" column="id">
<!-- select中uid是唯一标识(因为user中的id对应这account中的uid)。这里column写id是因为通过用户的id去查询Account表中的信息(对应account表中的uid) -->
</collection>
</resultMap>

延迟加载的两点:

  • mapper中内容的填写。即sql语句的写法,resultMap中标签的写法。
  • 开启全局配置文件的懒加载

11.2 Mybatis中的一级缓存

指的是MybatisSqlSession对象的缓存。当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供一块区域中。该区域的结构是一个Map。当我们再次查询同样的数据,Mybatis会先去SqlSession中查询是否有,有的话直接拿出来用。当SqlSession对象消失时,Mybatis的一级缓存也就消失了。

默认开启了一级缓存。当SqlSession关闭(sqlSession.close())时,缓存会自动消失。

同时sqlSession.clearCache(),也可以清楚缓存。

当数据库中的内容和缓存中数据不同时,Mybatis的做法:

一级缓存是SqlSession范围的缓存,当调用SqlSession的修改,添加,修改,commit()close()等方法时,就会清空一级缓存,直接从数据库中查询,并将查询的记录添加入缓存当中。

11.3 Mybatis中的二级缓存

指的是MybatisSqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。

二级缓存

二级缓存的使用步骤:

第一步:让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置,可以不用配置,默认是开启状态)

1
2
3
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>

第二步:让当前的映射文件支持二级缓存(在UserDao.xml中配置)

1
2
<!-- 开启user支持二级缓存-->
<cache/>

第三部:让当前的操作支持二级缓存(在select标签中配置,配置userCache值为true)

1
2
3
<select id="findById" parameterType="Integer" resultType="cn.lizhi.mybatis_01.domain.User" useCache="true">
select * from user where id=#{uid};
</select>

二级缓存中存放的内容是数据(json数据),而不是对象。当发起查询时,创建一个新的对象,然后将二级缓存中的数据填充到对象当中,所以前后两次查询中对象不同。

十二、Mybatis的注解开发

<packaging>jar</packaging>

Mybatis中针对CRUD的四个注解:

@Select@Insert@Update@Delete

在对应的Dao方法中直接写上对应的注解,注解中(@Select)写入操作数据库的语句即可。

注意:dao.XML配置文件会和注解产生冲突。故不能混用(dao.xml配置文件存放到其他地方或者不用)

12.1 Mybatis中的属性与字段不一致解决办法

使用注解@Results

例如:

1
2
3
4
5
6
@Results(id="userMap",value={
@Result(id=true,cloumn="id",property="userId"),
@Result(cloumn="username",property="userName"),
@Result(cloumn="address",property="userAddress"),
@Result(cloumn="sex",property="userSex")
})

id用于表示是否是主键,默认值是false。外部的id="userMap",用于指定唯一标识,可以让其他的注解进行复用。

如果需要让当前dao下其他的方法也能使用,指定注解@ResultMap.即:@ResultMap("userMap"),其中为数组类型,可以指定多个,例如:@ResultMap(value={"userMap",...})。可以理解为对应在dao.xml配置文件中的resultMap

12.2 Mybatis中的多表查询 – 注解方式

12.2.1 一对一

思想同dao.xml一样。通过配置@Results注解实现。

1
2
3
4
5
6
7
8
@Select("select * from account") 
@Results(id="accountMap",value={
@Result(id=true,cloumn="id",property="Id"),
@Result(cloumn="uid",property="uid"),
@Result(cloumn="money",property="money"),
// 以上完成了对account类对象的封装,下面是完成对User类对象的封装
@Result(property="user",column="uid",one=@One(select="全限定类名.方法名",fetchType = FetchType.EAGER(加载方式)))
})

select是指向如何查询封装对象的唯一标识 – 全限定类名.方法名。由于select直接定位到方法名,查询到具体的对象,所以在@Select语句中只查询了account(account中包含了user的属性,且在下方的注解中指定了uid去查询对应的user对象,进行封装) ,注解的方式包含了加载的方式,故采用这种查询方式

12.2.2 一对多

1
2
3
4
5
6
7
8
9
@Select("select * from user")
@Results(id="userMap",value={
@Result(id=true,cloumn="id",property="id"),
@Result(cloumn="username",property="name"),
@Result(cloumn="address",property="address"),
@Result(cloumn="sex",property="sex")
// 以上完成对user对象的封装,下方是account集合,继而封装account对象
@Result(property="accounts",column="id",many=@Many(select="全限定类名.方法名",fetchType = FetchType.LAZY(加载方式)))
})

以懒加载的形式,去理解这个查询语句及配置语句。

以上中,在@Result属性中需要关注的属性是select以及fetchType

12.3 Mybatis中的二级缓存 – 注解方式

Mybaits中一级缓存是默认打开的。

注解使用二级缓存的步骤:

  1. 二级缓存中,同样在全局配置中开启二级缓存。
  2. 在对应的Dao接口上配置全局的注解 – @CacheNamespace(blocking = true)

Comment