Mybatis框架学习

一、MyBatis 框架概述:

1、什么是mybatis

  • mybatis 是一个优秀的基于 java 的持久层框架,它内部封装了 jdbc,使开发者只需要关注 sql 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。

  • mybatis 通过 xml 或注解的方式将要执行的各种 statement 配置起来,并通过 java 对象和 statement 中sql 的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回。

  • 采用 ORM 思想解决了实体和数据库映射的问题,对 jdbc 进行了封装,屏蔽了 jdbc api 底层访问细节,使我们不用与 jdbc api 打交道,就可以完成对数据库的持久化操作。

2、mybatis在三层架构中的位置

在这里插入图片描述

二、 Mybatis 框架入门

1、基于XML的方式

1、创建 maven 工程:
在这里插入图片描述
2、添加 Mybatis的坐标:

  • 在 pom.xml 文件中添加 Mybatis3.4.5 的坐标,如下:
<dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
        </dependency>
    </dependencies>

3、编写 User 实体类:

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

    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 getAddress() {
        return address;
    }

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

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

4、编写持久层接口 IUserDao:

public interface IUserDao {
    /*
     *查询所有操作
     * */
    List<User> findAll();
}

5、编写持久层接口的映射文件 IUserDao.xml:

注意:

  • 创建位置:必须和持久层接口在相同的包中。
  • 名称:必须以持久层接口名称命名文件名,扩展名是.xml

例:都是在com.smk.dao包中
在这里插入图片描述

<?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.smk.dao.IUserDao">
    <select id="findAll" resultType="com.smk.domain.User">
        select * from user
    </select>
</mapper>

不要忘记在映射配置中告知mybatis要封装到哪个实体类中。

配置的方式:指定实体类的全限定类名 即:resultType="com.smk.domain.User"

6、 编写 SqlMapConfig.xml 配置文件:

<?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的主配置文件-->
<configuration>
    <!-- 配置环境-->
    <environments default="mysql">
        <!-- 配置mysql环境-->
        <environment id="mysql">
            <!--配置事物的类型-->
            <transactionManager type="JDBC"></transactionManager>
            <!--配置数据源/连接池2-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/my_batis?useUnicode=true&amp;characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value=""/>
            </dataSource>
        </environment>
    </environments>

    <!--指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件-->
    <mappers>
        <mapper resource="com/smk/dao/IUserDao.xml"></mapper>
    </mappers>
</configuration>

7、编写测试类:

public class mybatis {
    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接口对象
        IUserDao userDao = session.getMapper(IUserDao.class);
        //5.使用代理对象执行方法
        List<User> users = userDao.findAll();
        for (User user : users) {
            System.out.println(user);
        }
        //6.释放资源
        session.close();
        in.close();
    }
}

在测试类中运行:

在这里插入图片描述


2、基于注解的方式

在基于注解的的方式下就不再需要 IUserDao.xml配置文件,同时需要在SqlMapConfig.xml中的mapper配置时,使用class属性指定dao接口的全限定类名。

1、在持久层接口中添加注解:

public interface IUserDao {
    /*
     *查询所有操作
     * */
    @Select("select * from user")
    List<User> findAll();
}

2、修改 SqlMapConfig.xml:

<mappers>
        <mapper class="com.smk.dao.IUserDao"></mapper>
</mappers>

注意:在使用基于注解的 Mybatis 配置时,须移除 xml 的映射配置(IUserDao.xml)。

运行结果:

在这里插入图片描述

补充:

  • 不管使用XML还是注解配置, Mybatis是支持写dao实现类的。
  • 但在在实际开发中,都是越简便越好,所以都是采用不写dao实现类的方式。

三、 基于代理 Dao 实现 CRUD 操作

1、根据 ID 查询:

步骤:

1、在持久层接口中添加 findById 方法:

public interface IUserDao {
    //根据 id 查询
    User findById (Integer userId);
}

2、配置映射文件:

<mapper namespace="com.smk.dao.IUserDao">
    <select id="findById" resultType="com.smk.domain.User" parameterType="int">
        select * from user where id= #{id}
    </select>
</mapper>

解释

  • resultType 属性:用于指定结果集的类型。
  • parameterType 属性:用于指定传入参数的类型。
  • sql 语句中使用#{}字符:它代表占位符,相当于原来 jdbc 部分所学的,都是用于执行语句时替换实际的数据。具体的数据是由#{}里面的内容决定的。
  • #{}中内容的写法:由于数据类型是基本类型,所以此处可以随意写。

3、 在测试类添加测试:

public class mybatis {
    private InputStream in;
    private SqlSessionFactory factory;
    private SqlSession session;
    private IUserDao userDao;

    @Test
    public void testFindOne() {
        //6.执行操作
        User user = userDao.findById(41);
        System.out.println(user);
    }

    @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();
        //5.创建 Dao 的代理对象
        userDao = session.getMapper(IUserDao.class);
    }

    @After//在测试方法执行完成之后执行
    public void destroy() throws Exception {
        session.commit();//提交事务;在增删改时需要提交,查询时不需要。
        //7.释放资源
        session.close();
        in.close();
    }
}

运行结果:

在这里插入图片描述

2、保存操作:

同样的套路

1、在持久层接口中添加新增方法

  // 保存用户
  int saveUser(User user);

2、配置映射文件

<insert id="saveUser" parameterType="com.smk.domain.User">
        insert into user(username,birthday,sex,address) value (#{username},#{birthday},#{sex},#{address})
</insert>

解释:

#{}中内容的写法:

  • 由于保存方法的参数是 一个 User 对象,此处要写 User 对象中的属性名称。
    它用的是 ognl 表达式。

ognl 表达式:

  • 它是 apache 提供的一种表达式语言,全称是:Object Graphic Navigation Language 对象图导航语言,它是按照一定的语法格式来获取数据的。语法格式就是使用 #{对象.对象} 的方式。

  • #{user.username} 它会先去找 user 对象,然后在 user 对象中找到 username 属性,并调用 getUsername() 方法把值取出来。但是我们在 parameterType 属性上指定了实体类名称,所以可以省略 user 而直接写 username。

3、测试

 @Test
    public void testSave() {
        User user = new User();
        user.setUsername("modify User property");
        user.setAddress("北京市顺义区");
        user.setSex("男");
        user.setBirthday(new Date());
        System.out.println("保存操作之前:" + user);
        //5.执行保存方法
        userDao.saveUser(user);
        System.out.println("保存操作之后:" + user);
    }

注意

在实现 增删改 时需要进行控制事务的提交。

如何在mybatis 中如何控制事务提交?

  • 可以使用:session.commit(); 来实现事务提交。加入事务提交后的代码如下:
@After//在测试方法执行完成之后执行
public void destroy() throws Exception{
    session.commit();

    //7.释放资源
    session.close();
    in.close();
}

运行结果:

在这里插入图片描述
在这里插入图片描述
问题扩展:获取新增用户 id 的返回值

新增用户后,同时还要返回当前新增用户的 id 值,因为 id 是由数据库的自动增长来实现的,所以就相当于我们要在新增后将自动增长 auto_increment 的值返回。

    <insert id="saveUser" parameterType="com.smk.domain.User">
        <!-- 配置保存时获取插入的id   keypreperty:代表要返回的值对应实体类里的字段 ,keyColumn代表数据库的字段名-->
        <selectKey keyProperty="id" keyColumn="id" resultType="int">
            select last_insert_id();
        </selectKey>
        insert into user(username,birthday,sex,address) value (#{username},#{birthday},#{sex},#{address})
    </insert>

再次执行测试:

在这里插入图片描述
可以看到 id 的值已经获取到了。

3、用户更新:

1、在持久层接口中添加更新方法:

 //更新用户
    int updateUser(User user);

2、配置映射文件:

    <update id="updateUser" parameterType="com.smk.domain.User">
        update user set username=#{username},birthday=#{birthday},sex=#{sex}, address=#{address} where id=#{id}
    </update>

3、更新的测试方法:

 @Test
    public void testUpdateUser() throws Exception {
        //1.根据 id 查询
        User user = userDao.findById(52);
        //2.更新操作
        user.setAddress("北京市顺义区");
        int res = userDao.updateUser(user);
        System.out.println(res);
    }

运行结果:

在这里插入图片描述

4、 用户删除:

1、在持久层接口中添加删除方法:

 //删除用户
    int deleteUser(int uid);

2、配置映射文件:

<delete id="deleteUser" parameterType="int">
        delete from user where id=#{uid}
</delete>

3、删除的测试方法:

@Test
    public void testdeleteUser(){
        int res = userDao.deleteUser(52);
        System.out.println(res);
    }

至此基于mybatis的增删改查就完成了。


四、Mybatis 的参数深入

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

什么是ognl

Object Graphic Navigation Language
  对象     图          导航         语言

它是通过对象的取值方法来获取数据。在写法上把get给省略了。

比如:我们获取用户的名称

  • 类中的写法:user.getUsername()
  • OGNL表达式写法:user.username

mybatis中为什么能直接写username,而不用user.呢?

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

1、parameterType 配置参数

SQL 语句的传参是通过使用标签的 parameterType 属性来设定。该属性的取值可以是基本类型,引用类型(例如:String 类型),还可以是实体类类型(POJO 类)。同时也可以使用实体类的包装类。

2、传递 pojo 包装对象

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

例:

需求:根据用户名查询用户信息,查询条件放到 QueryVo 的 user 属性中。

1、编写 QueryVo:

public class QueryVo {
    private User user;

    public User getUser() {
        return user;
    }

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

2、编写持久层接口:

    //根据 QueryVo 中的条件查询用户
    List<User> findByVo(QueryVo vo);

3、配置映射文件:

    <!-- 根据用户名称模糊查询,参数变成一个 QueryVo 对象了 -->
    <select id="findByVo" resultType="com.smk.domain.User" parameterType="com.smk.domain.QueryVo">
        select * from user where username like #{user.username};
    </select>

4、添加测试类:

    @Test
    public void testFindByQueryVo() {
        QueryVo vo = new QueryVo();
        User user = new User();
        user.setUsername("%王%");
        vo.setUser(user);
        List<User> users = userDao.findByVo(vo);
        for (User u : users) {
            System.out.println(u);
        }
    }

运行结果:

在这里插入图片描述

五、Mybatis的输出结果封装

1、resultType的配置结果类型:

resultType 属性可以指定结果集的类型,它支持基本数据类型和实体类数据类型。

它和 parameterType 一样,如果注册过类型别名的,可以直接使用别名。

没有注册过的必须使用全限定类名。

例如:我们的实体类此时必须是全限定类名,同时,当是实体类名称时,实体类中的属性名称必须和查询语句中的列名(即数据库里的表名列名)保持一致,否则无法实现封装。

特殊情况示例

当实体类属性和数据库表的列名已经不一致时

1、实体类代码:

public class User2 {
    public class User implements Serializable {
        private Integer userId;
        private String userName;
        private Date userBirthday;
        private String userSex;
        private String userAddress;

        public Integer getUserId() {
            return userId;
        }

        public void setUserId(Integer userId) {
            this.userId = userId;
        }

        public String getUserName() {
            return userName;
        }

        public void setUserName(String userName) {
            this.userName = userName;
        }

        public Date getUserBirthday() {
            return userBirthday;
        }

        public void setUserBirthday(Date userBirthday) {
            this.userBirthday = userBirthday;
        }

        public String getUserSex() {
            return userSex;
        }

        public void setUserSex(String userSex) {
            this.userSex = userSex;
        }

        public String getUserAddress() {
            return userAddress;
        }

        public void setUserAddress(String userAddress) {
            this.userAddress = userAddress;
        }

        @Override
        public String toString() {
            return "User [userId=" + userId + ", userName=" + userName + ", userBirthday="
                    + userBirthday + ", userSex="
                    + userSex + ", userAddress=" + userAddress + "]";
        }
    }

}

2、映射配置:

   // 查询所有用户
    List<User> findAll();

3、测试查询结果:

    @Test
    public void testFindAll() {
        List<User2> users = userDao.findAll();
        for (User2 user : users) {
            System.out.println(user);
        }
    }

运行结果:

在这里插入图片描述
为什么名称会有值呢?

因为:mysql 在 windows 系统中不区分大小写!

应对措施

  1. 使用别名查询:
  2. 使用resultMap 结果类型封装

1、使用别名查询

即给实体类的属性名起对应数据的字段值别名

在这里插入图片描述

2、resultMap 结果类型

resultMap 标签可以建立查询的列名和实体类的属性名称不一致时建立对应关系。从而实现封装。

在 select 标签中使用 resultMap 属性指定引用即可。同时 resultMap 可以实现将查询结果映射为复杂类型的 pojo,比如在查询结果映射对象中包括 pojo 和 list 实现一对一查询和一对多查询。

实现步骤

1、定义 resultMap:

 <!-- 建立 Use实体和数据库表的对应关系
    type属性:指定实体类的全限定类名
    id 属性:给定一个唯一标识,是给查询 select 标签引用用的
    -->
    <resultMap type="com.smk.domain.User2" id="userMap">

        <id column="id" property="userId"/>
        <result column="username" property="userName"/>
        <result column="sex" property="userSex"/>
        <result column="address" property="userAddress"/>
        <result column="birthday" property="userBirthday"/>

    </resultMap>
  • id 标签:用于指定主键字段

  • result 标签:用于指定非主键字段

  • column 属性:用于指定数据库列名

  • property 属性:用于指定实体类属性名称

    2、映射配置:

    <!-- 配置查询所有操作 -->
    <select id="findAll" resultMap="userMap">
       select * from user
    </select>

3、测试结果:

在这里插入图片描述
可以看到查询正常了。

六、SqlMapConfig.xml配置文件

1、properties(属性)

在使用 properties 标签配置时,我们可以采用两种方式指定属性配置。

1、第一种 :

    <properties>
        <property name="jdbc.driver" value="com.mysql.jdbc.Driver"/>
        <property name="jdbc.url"
                  value="jdbc:mysql://localhost:3306/my_batis?useUnicode=true&amp;characterEncoding=utf8"/>
        <property name="jdbc.username" value="root"/>
        <property name="jdbc.password" value=""/>
    </properties>

2、第二种 :

在 classpath (类路径,resources目录即为根 类路径) 下定义 db.properties 文件
在这里插入图片描述

db.properties 文件内容:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/my_batis?useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=

注意:db.properties 文件中 数据库连接,指定编码格式不能用 &amp; 需改为 &

配置信息:

 <properties resource="db.properties"> </properties>

此时我们的 dataSource 标签就变成了引用上面的配置:

      <!--配置数据源/连接池-->
    <dataSource type="POOLED">
        <property name="driver" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="{$jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </dataSource>

2、typeAliases(类型别名):、

自定义别名:

在 SqlMapConfig.xml 中配置:

<typeAliases>
<!-- 单个别名定义 -->
<typeAlias type="com.itheima.domain.User" alias="user"/>

<!-- 批量别名定义,扫描整个包下的类,别名为类名(首字母大写或小写都可以)-->
<package name="com.smk.domain"/>
<package name="其它包"/>
</typeAliases>

3、mappers(映射器)

<mapper resource=" "/> 使用相对于类路径的资源,如:

<mapper resource="com/smk/dao/IUserDao.xml" />

<mapper class=" "/> 使用 mapper 接口类路径,如:

<mapper class="com.smk.dao.UserDao"/>

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

在 SqlMapConfig.xml 中配置:

<package name=""/> 注册指定包下的所有 mapper 接口,如:

<mappers>
     <package name="com.smk.dao"/>
</mappers>

注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。
在这里插入图片描述

七、Mybatis 连接池

1、Mybatis 的连接池技术:

在 web 开发中,常使用连接池技术来减少我们获取连接所消耗的时间;在 Mybatis 中也有连接池技术,但是它采用的是自己的连接池技术。在 Mybatis 的 SqlMapConfig.xml 配置文件中,通过 <dataSource type = ""> 来实现 Mybatis 中连接池的配置。

1.1、Mybatis 连接池的分类

mybatis连接池提供了3种方式的配置:

配置的位置:

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

type属性的取值:

  • POOLED 使用连接池的数据源
  • UNPOOLED 不使用连接池的数据源
  • JNDI 使用 JNDI 实现的数据源

具体结构如下:
在这里插入图片描述
相应地,MyBatis 内部分别定义了实现了 java.sql.DataSource 接口的 UnpooledDataSource,PooledDataSource 类来表示 UNPOOLED、POOLED 类型的数据源。

1.2、Mybatis 中数据源的配置

在SqlMapConfig.xml 进行配置

<dataSource type="POOLED">
    <property name="driver" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</dataSource>

Mybatis 在初始化时,根据<dataSource>的 type 属性来创建相应类型的的数据源 DataSource,即:

  • type=” POOLED “:MyBatis 会创建 PooledDataSource 实例。
  • type=” UNPOOLED “: MyBatis 会创建 UnpooledDataSource 实例。
  • type=” JNDI “:MyBatis 会从 JNDI 服务上查找 DataSource 实例,然后返回使用。

八、Mybatis的动态SQL语句

1、动态sql之<if>标签

  • 根据实体类的不同取值,使用不同的 SQL 语句来进行查询。比如在 id 如果不为空时可以根据 id 查询,如果 username 不为空时还要加入用户名作为条件。这种情况在我们的多条件组合查询中经常会碰到。

1、持久层 Dao 接口:

    //根据用户信息,查询用户列表
    List<User> findByUser(User user);

2、持久层 Dao 映射配置:

    <select id="findByUser" resultMap="userMap" parameterType="user">
        select * from user where 1=1
        <if test="userName!=null and userName != '' ">
            and username like #{userName}
        </if>

        <if test="userAddress != null">
            or address like #{userAddress}
        </if>
    </select>

注:where 1=1 是为了避免 where 关键字后面的第一个词直接就是 “ and “ 而导致语法错误。

3、 测试:

 @Test
    public void testFindByUser() {
        User u = new User();
        u.setUserName("%王%");
        u.setUserAddress("%顺义%");
        //6.执行操作
        List<User> users = userDao.findByUser(u);
        for (User user : users) {
            System.out.println(user);
        }
    }

运行结果:

在这里插入图片描述
可以看到,带 “王” 的用户和地址带 “顺义” 的用户信息全查询出来了。

2、动态sql 之 <where>标签

为了简化上面 where 1=1 的条件拼装,可以采用 <where> 标签来简化开发。

1、持久层 Dao 映射配置:

    <select id="findByUser" resultMap="userMap" parameterType="user">
        select * from user
        <where>
            <if test="userName!=null and userName != '' ">
                and username like #{userName}
            </if>
            <if test="userAddress != null">
                or address like #{userAddress}
            </if>
        </where>
    </select>

3、动态标签之<foreach>标签

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

1、在 QueryVo 中加入一个 List 集合用于封装参数:

public class QueryVo {

    private User user;
    private List<Integer> ids;

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

    public void setIds(List<Integer> ids) {
        this.ids = ids;
    }

    public User getUser() {
        return user;
    }

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

}

2、持久层 Dao 接口:

    // 根据 id 集合查询用户
    List<User> findInIds(QueryVo vo);

3、持久层 Dao 映射配置:

    <select id="findInIds" resultMap="userMap" parameterType="queryVo">
        select * from user
        <where>
            <if test='ids !=null and ids.size>0'>
                <foreach collection="ids" open="id in (" close=")" item="id" separator=",">
                    #{id}
                </foreach>
            </if>
        </where>
    </select>

SQL 语句:

  • select 字段 from user where id in (?)

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

  • collection:代表要遍历的集合元素,注意编写时不要写#{}
  • open:代表语句的开始部分
  • close:代表结束部分

4、 测试:

    @Test
    public void testFindInIds() {
        QueryVo vo = new QueryVo();
        List<Integer> ids = new ArrayList<Integer>();
        ids.add(41);
        ids.add(42);
        ids.add(43);
        ids.add(46);
        ids.add(57);
        vo.setIds(ids);
        //6.执行操作
        List<User> users = userDao.findInIds(vo);
        for (User user : users) {
            System.out.println(user);
        }
    }

运行结果:

在这里插入图片描述

4、Mybatis 中简化编写的 sql 片段

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

1、定义代码片段:

    <!-- 抽取重复的语句代码片段 -->
    <sql id="defaultSql">
        select * from user
    </sql>

2、引用代码片段:

    <!-- 配置查询所有操作 -->
    <select id="findAll" resultType="user">
        <include refid="defaultSql"></include>
    </select>

    <!-- 根据 id 查询 -->
    <select id="findById" resultType="UsEr" parameterType="int">
        <include refid="defaultSql"></include>
        where id = #{uid}
    </select>

九、Mybatis多表查询一对一(多对一)

**1、一对一查询(多对一)**:

以最为简单的用户和账户的模型来分析 Mybatis 多表关系。用户为 User 表,账户为Account表。
一个用户(User)可以有一个或多个账户(Account)。

需求

查询所有账户信息,关联查询下单用户信息(只要用户名和地址)。

注意

因为一个账户信息只能供某一个用户使用,所以从查询账户信息出发,关联查询用户信息为一对一查询。如果从用户信息出发,查询用户下的账户信息则为一对多查询,因为一个用户可以有多个账户。

方式一

1、定义账户信息的实体类:

public class Account implements Serializable {
    private Integer id;
    private Integer uid;
    private Double money;

    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;
    }

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

2、定义 AccountUser 类:

为了能够封装上面 SQL 语句的查询结果,定义 AccountCustomer 类中要包含账户信息同时还要包含用户信息,所以在定义 AccountUser 类时可以继承 User 类。

public class AccountUser extends Account implements Serializable {
    private String username;
    private String address;

    public String getUsername() {
        return username;
    }

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

    public String getAddress() {
        return address;
    }

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

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

3、定义持久层 Dao 接口:

    /**
     * 查询所有用户,带有用户名和地址信息
     * @return
     */
    List<AccountUser> findAllAccount();

4、定义 IAccountDao.xml 文件中的查询配置信息:

    <!--查询所有账户同是包含用户名和地址信息-->
    <select id="findAllAccount" resultType="accountuser">
      select a.*,u.username,u.address from account a,user u where a.uid =u.id;
    </select>

5、创建 AccountTest 测试类:

    @Test
    public void testFindAllAccount() {
        List<AccountUser> aus = accountDao.findAllAccount();
        for (AccountUser au : aus) {
            System.out.println(au);
        }
    }

运行结果:

在这里插入图片描述

小结:

定义专门的 po 类作为输出类型,其中定义了 sql 查询结果集所有的字段。此方法较为简单,企业中使用普遍。

po: (persistant object)持久对象,可以看成是与数据库中的表相映射的java对象。

方式二

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

我们可以在 Account 类中加入一个 User 类的对象来代表这个账户是哪个用户的。

1、在 Account 类中加入 User 类的对象作为 Account 类的一个属性:

public class Account implements Serializable {
    private Integer id;
    private Integer uid;
    private Double money;

    private User user;

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.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;
    }

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

2、修改 AccountDao 接口中的方法:

   List<Account> findAllAccount();

把 AccountUser 改为 Account

3、重新定义 AccountDao.xml 文件:

<!-- 建立对应关系 -->
    <resultMap type="account" id="accountMap">
        <id column="aid" property="id"/>
        <result column="uid" property="uid"/>
        <result column="money" property="money"/>

        <!-- 它是用于指定 从表 方的引用实体属性的 -->
        <association property="user" javaType="user">
            <id column="id" property="id"/>
            <result column="username" property="username"/>
            <result column="sex" property="sex"/>
            <result column="birthday" property="birthday"/>
            <result column="address" property="address"/>
        </association>
    </resultMap>

    <select id="findAllAccount" resultMap="accountMap">
      select u.*,a.id as aid,a.uid,a.money from account a,user u where a.uid =u.id;
    </select>

注:

  • a.id as aid :这里两个表的字段重复了所以需要起别名。
  • javaType=” user “:即指定数据类型为user类,全限定类名或者别名(此处为别名)。

4、加入测试方法:

    @Test
    public void testFindAllAccount() {
        List<Account> accounts = accountDao.findAllAccount();
        for (Account account : accounts) {
            System.out.println(account);
            System.out.println(account.getUser());
        }
    }

测试结果:

在这里插入图片描述
2、 一对多查询

需求:

  • 查询所有用户信息及用户关联的账户信息。

分析:

  • 用户信息和他的账户信息为一对多关系,并且查询过程中如果用户没有账户信息,此时也要将用户信息查询出来,此时应该使用左外连接查询比较合适。

1、User 类加入 List <Account>

public class User implements Serializable {

    private Integer Id;
    private String username;
    private String Address;
    private String Sex;
    private Date Birthday;

    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 String getAddress() {
        return Address;
    }

    public void setAddress(String address) {
        Address = address;
    }

    public String getSex() {
        return Sex;
    }

    public void setSex(String sex) {
        Sex = sex;
    }

    public Date getBirthday() {
        return Birthday;
    }

    public void setBirthday(Date birthday) {
        Birthday = birthday;
    }

    @Override
    public String toString() {
        return "User{" +
                "Id=" + Id +
                ", username='" + username + '\'' +
                ", Address='" + Address + '\'' +
                ", Sex='" + Sex + '\'' +
                ", Birthday=" + Birthday +
                '}';
    }
}

2、 Dao 接口中加入查询方法:

    List<User> findAll();

3、Dao 映射文件配置:

    <resultMap type="user" id="userMap">
        <id column="id" property="id"></id>
        <result column="username" property="username"/>
        <result column="address" property="address"/>
        <result column="sex" property="sex"/>
        <result column="birthday" property="birthday"/>
        <!-- collection 是用于建立一对多中集合属性的对应关系
        ofType 用于指定集合元素的数据类型
        -->
        <collection property="accounts" ofType="account">
            <id column="aid" property="id"/>
            <result column="uid" property="uid"/>
            <result column="money" property="money"/>
        </collection>
    </resultMap>

    <!--查询所有-->
    <select id="findAll" resultMap="userMap">
        select u.*,a.id as aid ,a.uid,a.money from user u left outer join account a on u.id =a.uid
    </select>

注:
collection:

  • 部分定义了用户关联的账户信息。表示关联查询结果集

property=” accList “ :

  • 关联查询的结果集存储在 User 对象的上哪个属性。

ofType=” account “:

  • 指定关联查询的结果集中的对象类型即List中的对象类型。此处可以使用别名,也可以使用全限定名。

4、 测试方法:

    @Test
    public void testFindAll() {
        List<User> users = userDao.findAll();
        for (User user : users) {
            System.out.println("用户的信息:");
            System.out.println(user);
            System.out.println(user.getAccounts());
        }
    }

5、运行结果:

在这里插入图片描述

十、 Mybatis 多表查询——多对多

1、实现 Role 到 User 多对多

1、编写 Role 实体类:

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 + '\'' +
                '}';
    }
}

2、编写 Role 持久层接口:

    /**
     * 查询所有角色
     * @return
     */
    List<Role> findAll();

3、编写映射文件:

<mapper namespace="com.itheima.dao.IRoleDao">
    <!--定义role表的ResultMap-->
    <resultMap id="roleMap" type="role">
        <id property="roleId" column="rid"></id>
        <result property="roleName" column="role_name"></result>
        <result property="roleDesc" column="role_desc"></result>
        <collection property="users" ofType="user">
            <id column="id" property="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>

    </resultMap>
    <!--查询所有-->
    <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>

4、测试类:

    /**
     * 测试查询所有
     */
    @Test
    public void testFindAll() {
        List<Role> roles = roleDao.findAll();
        for (Role role : roles) {
            System.out.println("---每个角色的信息----");
            System.out.println(role);
            System.out.println(role.getUsers());
        }
    }

运行结果:

在这里插入图片描述
2、实现 User 到 Role 的多对多

大体步骤和上面同理,其中sql语句需改为:

 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.rid
       left outer join role r on r.id = ur.uid

十一、 Mybatis 延迟加载策略

通过前面的学习,我们已经掌握了 Mybatis 中一对一,一对多,多对多关系的配置及实现,可以实现对象的关联查询。实际开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的账户信息。此时就是我们所说的延迟加载。

1、何为延迟加载?

就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载。

好处:

  • 先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。

坏处:

  • 因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。

2、使用 assocation 实现延迟加载

需求:

  • 查询账户(Account) 信息并且关联查询用户(User) 信息。如果先查询账户(Account) 信息即可满足要求,当我们需要查询用户(User) 信息时再查询用户(User) 信息。把对用户(User) 信息的按需去查询就是延迟加载。

1、DAO 接口:

  List<Account> findAll();

2、持久层映射文件:

<?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.itheima.dao.IAccountDao">

    <!-- 建立对应关系 -->
    <resultMap type="account" id="accountMap">
        <id column="id" property="id"/>
        <result column="uid" property="uid"/>
        <result column="money" property="money"/>

        <!-- 它是用于指定从表方的引用实体属性的 -->
        <association property="user" javaType="user"
                     select="com.itheima.dao.IUserDao.findById"
                     column="uid">
        </association>
    </resultMap>

    <select id="findAll" resultMap="accountMap">
      select * from account
    </select>
</mapper>

其中:

  • select: 填写我们要调用的 select 映射的 id
  • column : 填写我们要传递给 select 映射的参数

3、User 的持久层接口和映射文件:

User findById(Integer userId); 
<!--根据 id 查询-->
<select id="findById" resultType="user" parameterType="int" >
    select * from user where id = #{uid}
</select>

4、开启 Mybatis 的延迟加载策略:

在 SqlMapConfig.xml 中配置

    <!--开启延迟加载的支持-->
    <settings>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
  • lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。
  • aggressiveLazyLoading:当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载。

5、测试查询用户信息时:

@Test
    public void testFindAll() {
        List<Account> accounts = accountDao.findAll();
        for (Account account : accounts) {
            System.out.println("每个account的信息:--------------");
            System.out.println(account);
            System.out.println(account.getUser());
        }
    }

测试结果:

在这里插入图片描述
可以看到SQL语句全部执行了。

6、测试只查账户信息不查用户信息:

@Test
    public void testFindAll() {
        List<Account> accounts = accountDao.findAll();
       /* for (Account account : accounts) {
            System.out.println("每个account的信息:--------------");
            System.out.println(account);
            System.out.println(account.getUser());
        }*/
    }

运行结果:

在这里插入图片描述
可以看到只执行了一条SQL语句,即执行了延迟加载。

3、使用 Collection 实现延迟加载

同样我们也可以在一对多关系配置的<collection>结点中配置延迟加载策略。<collection>结点中也有 select 属性,column 属性。

需求:

  • 完成加载用户对象时,查询该用户所拥有的账户信息。

1、 User 实体类:

public class User implements Serializable {

    private Integer Id;
    private String username;
    private String Address;
    private String Sex;
    private Date Birthday;

    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 String getAddress() {
        return Address;
    }

    public void setAddress(String address) {
        Address = address;
    }

    public String getSex() {
        return Sex;
    }

    public void setSex(String sex) {
        Sex = sex;
    }

    public Date getBirthday() {
        return Birthday;
    }

    public void setBirthday(Date birthday) {
        Birthday = birthday;
    }

    @Override
    public String toString() {
        return "User{" +
                "Id=" + Id +
                ", username='" + username + '\'' +
                ", Address='" + Address + '\'' +
                ", Sex='" + Sex + '\'' +
                ", Birthday=" + Birthday +
                '}';
    }
}

2、用户(User)和账户(Account)持久层接口的方法:

    /**
     * 查询所有用户
     *
     * @return
     */
    List<User> findAll();

    /**
     * 根据用户 id 查询账户信息
     * @param uid
     * @return
     */
    List<User> findByUid(Integer uid);

3、编写用户持久层映射配置:

<resultMap type="user" id="userMap">
        <id column="id" property="id"></id>
        <result column="username" property="username"/>
        <result column="address" property="address"/>
        <result column="sex" property="sex"/>
        <result column="birthday" property="birthday"/>

        <!-- collection 是用于建立一对多中集合属性的对应关系 
        ofType 用于指定集合元素的数据类型
        -->
        <collection property="accounts" ofType="account"
                    select="com.itheima.dao.IAccountDao.findByUid"
                    column="id">
        </collection>
    </resultMap>

    <!--查询所有-->
    <select id="findAll" resultMap="userMap">
        select * from user
    </select>

<collection>标签:

  • 主要用于加载关联的集合对象

select 属性:

  • 用于指定查询 account 列表的 sql 语句,所以填写的是该 sql 映射的 id

column 属性:

  • 用于指定 select 属性的 sql 语句的参数来源,上面的参数来自于 user 的 id 列,所以就写成 id 这一个字段名了

4、编写账户持久层映射配置:

    <!-- 根据用户 id 查询账户信息 -->
    <select id="findByUid" resultType="account" parameterType="int">
        select * from account where uid = #{uid}
    </select>

5、延时加载 测试只加载用户信息:

 @Test
    public void testFindAll() {
        List<User> users = userDao.findAll();
      /*  for (User user : users) {
            System.out.println("用户的信息:");
            System.out.println(user);
            System.out.println(user.getAccounts());
        }*/
    }

测试结果:

在这里插入图片描述

可以发现并没有加载 Account 账户信息。

十二、 Mybatis 缓存

像大多数的持久化框架一样,Mybatis 也提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能。

Mybatis 中缓存分为一级缓存,二级缓存。

在这里插入图片描述
1、Mybatis 一级缓存

一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就存在。

验证一级缓存的存在:

测试方法:

  @Test
    public void testFirstCache() {
        User user1 = userDao.findById(41);
        System.out.println(user1);

        User user2 = userDao.findById(41);
        System.out.println(user2);

        System.out.println(user1 == user2);
    }

测试结果:

在这里插入图片描述
在这里插入图片描述
可以发现,虽然在上面的代码中查询了两次,但最后只执行了一次数据库操作,这就是 Mybatis 提供给我们的一级缓存在起作用了。因为一级缓存的存在,导致第二次查询 id 为 41 的记录时,并没有发出 sql 语句从数据库中查询数据,而是从一级缓存中查询。

测试一级缓存的清空:

测试方法:

 @Test
    public void testFirstCache() {
        User user1 = userDao.findById(41);
        System.out.println(user1);
        // sqlSession.close();

        //再次获取 SqlSession 对象
        // sqlSession = factory.openSession();
        //userDao = sqlSession.getMapper(IUserDao.class);

        sqlSession.clearCache();//此方法也可以清空缓存

        User user2 = userDao.findById(41);
        System.out.println(user2);
        System.out.println(user1 == user2);

    }

测试结果:

在这里插入图片描述
在这里插入图片描述

当执行 sqlSession.clearCache() 后,再次获取sqlSession并查询id=41的User对象时,又重新执行了sql语句,从数据库进行了查询操作。

一级缓存的分析

一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除,commit(),close()方法时,就会清空一级缓存。

在这里插入图片描述
当第一次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库查询用户信息。

得到用户信息,将用户信息存储到一级缓存中。

如果 sqlSession 去执行 commit 操作(执行插入、更新、删除),清空 SqlSession 中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。

第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息(此时已经没有一级缓存了),如果缓存中有,则直接从缓存中获取用户信息,没有则重新发起查询。

2、Mybatis 二级缓存

它是 Mybatis 中 SqlSessionFactory 对象的缓存。由同一个 SqlSessionFactory 对象创建的 SqlSession 共享其缓存。

在这里插入图片描述
假设:

sqlSession1 去查询用户信息,查询到用户信息会将查询数据存储到二级缓存中。

如果 SqlSession3 去执行相同 mapper 映射下 sql,执行 commit 提交,将会清空该 mapper 映射下的二级缓存区域的数据。

sqlSession2 去查询与 sqlSession1 相同的用户信息,首先会去缓存中找是否存在数据,如果存在直接从缓存中取出数据,,否则重新发起查询(本例)。

二级缓存的使用步骤

1、让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置):

<settings>
    <!-- 开启二级缓存的支持 -->
    <setting name="cacheEnabled" value="true"/>
</settings>

因为 cacheEnabled 的取值默认就为 true,所以这一步可以省略不配置。

true 代表开启二级缓存;为false 代表不开启二级缓存。

2、让当前的映射文件支持二级缓存(在IUserDao.xml中配置):

    <!-- 开启二级缓存的支持 -->
    <cache/>

3、让当前的操作支持二级缓存(在select标签中配置):

<!-- 根据 id 查询 -->
<select id="findById" resultType="user" parameterType="int" useCache="true">
    select * from user where id = #{uid}
</select>

将 IUserDao.xml 映射文件中的<select>标签中设置 useCache=” true “ 代表当前这个 statement 要使用二级缓存,如果不使用二级缓存可以设置为 false。

注意:针对每次查询都需要最新的数据 sql,要设置成 useCache=false,禁用二级缓存。

测试:

 @Test
 public void testFirstLevelCache(){
     SqlSession sqlSession1 = factory.openSession();
     IUserDao dao1 = sqlSession1.getMapper(IUserDao.class);
     User user1 = dao1.findById(41);
     System.out.println(user1);
     sqlSession1.close();//一级缓存消失

     SqlSession sqlSession2 = factory.openSession();
     IUserDao dao2 = sqlSession2.getMapper(IUserDao.class);
     User user2 = dao2.findById(41);
     System.out.println(user2);
     sqlSession2.close();

     System.out.println(user1 == user2);
 }

运行结果:

在这里插入图片描述
在这里插入图片描述
经过上面的测试,发现执行了两次查询,并且在执行第一次查询后,我们关闭了一级缓存,再去执行第二次查询时,我们发现并没有对数据库发出 sql 语句,所以此时的数据就只能是来自于我们所说的二级缓存。

user1 = user2 = false 的原因是 二级缓存存放的是 数据 而不是对象,每次查询都会创建一个新的对象。

十三、 Mybatis 注解开发

1、mybatis 的常用注解

@Insert: 实现新增
@Update: 实现更新
@Delete: 实现删除
@Select: 实现查询

@Result: 实现结果集封装
@Results: 可以与@Result 一起使用,封装多个结果集
@ResultMap: 实现引用@Results 定义的封装

@One: 实现一对一结果集封装
@Many: 实现一对多结果集封装

@SelectProvider: 实现动态 SQL 映射
@CacheNamespace: 实现注解二级缓存的使用

2、使用 Mybatis 注解实现基本 CRUD

1、实体类:

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

    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 String getAddress() {
        return address;
    }

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

    public String getSex() {
        return sex;
    }

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

    public Date getBirthday() {
        return birthday;
    }

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

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

2、使用注解方式开发持久层接口:

public interface IUserDao {
    /**
     * 查询所有
     * @return
     */
    @Select("select * from user")
    List<User> findAll();

    /**
     * 根据 id 查询一个用户
     * @param userId
     * @return
     */

    @Select("select * from user where id = #{uid} ")
    User findById(Integer userId);


    /**
     * 插入操作
     * @param user
     * @return
     */
    @Insert("insert into user(username,sex,birthday,address)values(#{username},#{sex},#{birthday},#{address})")
    int saveUser(User user);

    /**
     * 更新操作
     * @param user
     * @return
     */
    @Update("update user set username=#{username}, address =#{address}, sex =#{sex}, birthday =#{birthday}where id =#{id}")
    int updateUser(User user);

    /**
     * 删除用户
     * @param userId
     * @return
     */
    @Delete("delete from user where id = #{uid} ")
    int deleteUser(Integer userId);

    /**
     * 查询使用聚合函数
     * @return
     */
    @Select("select count(*) from user ")
    int findTotal();

    /**
     * 模糊查询
     * @param name
     * @return
     */
    @Select("select * from user where username like #{username} ")
    List<User> findByName(String name);
}

3、SqlMapConfig 配置文件:

<?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>
    <!--引入外部文件-->
    <properties resource="jdbcConfig.properties"/>
    <!--配置别名-->
    <typeAliases>
        <package name="com.smk.domain"/>
    </typeAliases>
    <environments default="mysql">
        <environment id="mysql">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 配置 dao 接口的位置,它有两种方式
    第一种:使用 mapper 标签配置 class 属性
    第二种:使用 package 标签,直接指定 dao 接口所在的包
    -->
    <mappers>
        <package name="com.smk.dao"/>
    </mappers>
</configuration>

4、测试方法:

测试方法和xml映射配置开发是一样的。

3、使用注解实现复杂关系映射开发

实现复杂关系映射之前,可以在映射文件中通过配置 <resultMap> 来实现,在使用注解开发时我们需要借助@Results 注解,@Result 注解,@One 注解,@Many 注解。

3.1、当POJO属性和数据库字段不一致时

例:
1、 实体类:

    private Integer userId;
    private String userName;
    private String userAddress;
    private String userSex;
    private Date userBirthday;

2、dao接口:

public interface IUserDao {
    /**
     * 查询所有
     *
     * @return
     */
    @Select("select * from user")
    @Results(id = "userMap", value = {
            @Result(id = true, column = "id", property = "userId"),
            @Result(column = "username", property = "userName"),
            @Result(column = "address", property = "userAddress"),
            @Result(column = "sex", property = "userSex"),
            @Result(column = "birthday", property = "userBirthday")
    })
    List<User> findAll();

    /**
     * 根据 id 查询用户
     *
     * @param userId
     * @return
     */
    @Select("select * from user where id = #{uid} ")
    @ResultMap("userMap")
    User findById(Integer userId);


    /**
     * 模糊查询
     *
     * @param name
     * @return
     */
    @Select("select * from user where username like #{username}")
    @ResultMap("userMap")
    List<User> findByName(String name);
}

其中:

@Results 注解代替的是标签<resultMap>

@Resutl 注解代替了 <id>标签和<result>标签

@Result 中 属性介绍:

  • id 是否是主键字段
  • column 数据库的列名
  • property 需要装配的属性名

@Result定义后,在其他方法处,便可以使用该@Results定义的resultMap了。

3.2、使用注解实现一对一复杂关系映射

1、User 实体类及 Account 实体类:

和上面xml配置方式相同。

2、账户的持久层接口及注解配置:

public interface IAccountDao {
    @Select("select * from account")
    @Results(id = "accountMap", value = {
            @Result(id = true, column = "id", property = "id"),
            @Result(column = "uid", property = "uid"),
            @Result(column = "money", property = "money"),
            @Result(property = "user", column = "uid", one = @One(select = "com.smk.dao.IUserDao.findById", fetchType = FetchType.EAGER))

    })
    List<Account> findAll();
}

@One 注解(一对一):

  • 代替了<assocation>标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。

@One 注解属性介绍:

  • select 指定用来多表查询的 sqlmapper
  • fetchType 会覆盖全局的配置参数 lazyLoadingEnabled。。

使用格式:

  • @Result(column=””,property=””,one=@One(select=””))

3、用户的持久层接口及注解配置:

public interface IUserDao {
    /**
     * 查询所有
     *
     * @return
     */
    @Select("select * from user")
    @Results(id = "userMap", value = {
            @Result(id = true, column = "id", property = "userId"),
            @Result(column = "username", property = "userName"),
            @Result(column = "address", property = "userAddress"),
            @Result(column = "sex", property = "userSex"),
            @Result(column = "birthday", property = "userBirthday")
    })
    List<User> findAll();

    /**
     * 根据 id 查询用户
     *
     * @param userId
     * @return
     */
    @Select("select * from user where id = #{uid} ")
    @ResultMap("userMap")
    User findById(Integer userId);
}

运行结果:

在这里插入图片描述
3.3、使用注解实现一对多复杂关系映射

1、User 实体类及 Account 实体类:

  • 和上面xml配置方式相同。

2、用户的持久层接口及注解配置:

public interface IUserDao {
    /**
     * 查询所有
     *
     * @return
     */
    @Select("select * from user")
    @Results(id = "userMap", value = {
            @Result(id = true, column = "id", property = "userId"),
            @Result(column = "username", property = "userName"),
            @Result(column = "address", property = "userAddress"),
            @Result(column = "sex", property = "userSex"),
            @Result(column = "birthday", property = "userBirthday"),
            @Result(property = "accounts", column = "id", many = @Many(
                    select = "com.smk.dao.IAccountDao.findByUid",
                    fetchType = FetchType.LAZY))
    })
    List<User> findAll();
}

3、账户的持久层接口及注解配置:

public interface IAccountDao {

    // 根据用户 id 查询用户下的所有账户
    @Select("select * from account where uid = #{uid} ")
    List<Account> findByUid(Integer userId);
}

4、添加测试方法:

    //测试查询所有
    @Test
    public void testFindAll() {
        List<User> users = userDao.findAll();
        for (User user : users) {
            System.out.println("-----每个用户的内容-----");
            System.out.println(user);
            System.out.println(user.getAccounts());
        }
    }

运行结果:

在这里插入图片描述
测试延时加载:

 @Test
    public void testFindAll() {
        List<User> users = userDao.findAll();
       /* for (User user : users) {
            System.out.println("-----每个用户的内容-----");
            System.out.println(user);
            System.out.println(user.getAccounts());
        }*/
    }

运行结果:

在这里插入图片描述

3.4、mybatis 基于注解的二级缓存

1、在 SqlMapConfig 中开启二级缓存支持:

<!-- 配置二级缓存 -->
<settings>
    <!-- 开启二级缓存的支持 -->
    <setting name="cacheEnabled" value="true"/>
</settings>

2、在持久层接口中使用注解配置二级缓存:

@CacheNamespace(blocking=true)//mybatis 基于注解方式实现配置二级缓存
public interface IUserDao {}

🆗,至此mybatis的内容就总结完了( ̄︶ ̄)↗ 


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 2058751973@qq.com

×

喜欢就点赞,疼爱就打赏