初识Mybatis

343 阅读16分钟

MyBatis

初识Mybatis

1.导入mybatis相关依赖
<dependencies>
    <!--mybatis的核心依赖-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.4.5</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
    <!--mysql驱动依赖-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.42</version>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.12</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.12</version>
    </dependency>
</dependencies>
2.创建pojo,将pojo和数据表进行关联映射,定义接口

创建Bean :User类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
}

定义数据持久层接口类:UserDao

public interface UserDao {
    List<User> queryAll();
}
3.创建mybatis核心配置文件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">
<configuration>
    <!--配置mybatis的环境-->
    <environments default="mysql">
        <!--配置mysql的环境-->
        <environment id="mysql">
            <!--配置事务的类型,我们使用JDBC的事务-->
            <transactionManager type="JDBC"></transactionManager>
            <!--
                配置连接数据库的信息,用的是数据源(连接池)
                type: POOLED 使用默认的数据库连接
                      UNPOOLED
            -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false"/>
                <property name="username" value="root"/>
                <property name="password" value="rootroot"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="com/duoduo/dao/UserDao.xml"></mapper>
    </mappers>
</configuration>
4.创建接口的映射文件

接口所在的目录和接口文件的目录保持一致。

1.png

<?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.duoduo.dao.UserDao">
    <!--
        配置查询所有的操作
        id:方法名称  和接口的方法名称对应
        parameterType: 参数值类型(全限定名)
        resultType: 返回值类型(全限定名)
    -->
    <select id="queryAll" resultType="com.duoduo.pojo.User">
        select * from user
    </selct>
</mapper>
5.编写测试代码
@Test
public void testQueryAll() throws Exception {
    //加载mybatis的核心配置文件
    InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
    //创建sqlSessionFactoryBuilder
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    //创建SqlSessionFactory对象(构建者设计模式,屏蔽了SqlSessionFactory创建的细节)
    SqlSessionFactory factory = builder.build(inputStream);
    //创建SqlSession对象(工厂模式)
    SqlSession session = factory.openSession();
    //通过SqlSession生成接口的代理对象(生成动态代理的思想)
    UserDao userDao = session.getMapper(UserDao.class);
    
    List<User> users = userDao.queryAll();
    users.forEach(user -> System.out.println(user));
    
    //关闭资源
    session.close();
    inputStream.close();
}

测试结果:

2.png

单表操作(UserDao.xml)

单表的查询、更新
<?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.duoduo.dao.UserDao">
    <!--
        查询所有的用户信息
    -->
    <select id="queryAllUser" resultType="com.duoduo.pojo.User">
        select * from user
    </select>
    <!--
        根据id查询用户信息
        id:定义接口的名称
        resultType: 方法的返回值类型
        parameterType: 形参的数据类型
    -->
    <select id="queryUserById" resultType="com.duoduo.pojo.User" parameterType="int">
        select * from user where id = #{id}
    </select>

    <insert id="addUser" parameterType="com.duoduo.pojo.User" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
        <!--
            selectKey 获取自增长主键的标签
                keyColum:数据表中主键的字段名称
                keyProperty:实体类映射主键字段的属性
                resultType:主键的数据类型
                select 240308 是mysql的系统函数,专门用来获取新增之后主键的值
        -->
        <!--<selectKey keyColumn="id" keyProperty="id" resultType="Integer">
            select 240308
        </selectKey>-->
        insert into user(username, birthday, sex, address) VALUES (#{username},#{birthday},#{sex},#{address})
    </insert>

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

    <delete id="deleteUserById" parameterType="int">
        delete from user where id = #{id}
    </delete>
</mapper>
模糊查询

UserDao.xml 配置文件的编写

<select id="queryDimUserByUsername" parameterType="string" resultType="com.duoduo.pojo.User">
    select * from user where username like #{username}
</select>
<select id="queryDimUserByUsername2" parameterType="string" resultType="com.duoduo.pojo.User">
    select * from user where username like '%${value}%'
</select>

测试类的编写,不同的写法测试类也不同

@Test
public void testDimQuery(){
    List<User> users = userDao.queryDimUserByUsername("%王%");
    users.forEach(user -> System.out.println(user));
    session.commit();
}

@Test
public void testDimQuery2(){
    List<User> users = userDao.queryDimUserByUsername2("王");
    users.forEach(user -> System.out.println(user));
    session.commit();
}

3.png

#{} 和 ${} 的区别:

#{}表示一个占位符号 通过#{}可以实现 preparedStatement 向占位符中设置值,自动进行 java 类型和 jdbc 类型转换, #{}可以有效防止 sql 注入。 #{}可以接收简单类型值或 pojo 属性值。 如果 parameterType 传输单个简单类 型值,#{}括号中可以是 value 或其它名称。

表示拼接sql串通过{}表示拼接 sql 串 通过{}可以将 parameterType 传入的内容拼接在 sql 中且不进行 jdbc 类型转换, 可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,{}可以接收简单类型值或 pojo 属性值,如果 parameterType 传输单个简单类型值,**{}括号中只能是value。**(使用的是statement)

4.png

在接口中传入多个参数的问题

数据控制层Dao

List<User> queryUserByMap(Map<String,Object> map);

UserDao.xml 配置文件的编写

<select id="queryUserByMap" parameterType="map" resultType="com.duoduo.pojo.User">
    select * from user where sex = #{sex} and address = #{address}
</select>

测试类:

public void testQueryMap(){
    Map<String,Object> map = new HashMap<>();
    map.put("sex","男");
    map.put("address","广东深圳");
    List<User> users = userDao.queryUserByMap(map);
    users.forEach(user -> System.out.println(user));
}

5.png

pojo封装同一类型参数的对象
@Getter
@Setter
public class QueryIds {
    private List<Integer> ids;
}

数据控制层Dao

List<User> queryUserByIds(QueryIds queryIds);

UserDao.xml 配置文件的编写

<select id="queryUserByIds" parameterType="com.duoduo.pojo.QueryIds" resultType="com.duoduo.pojo.User">
    select * from user where id in
    <foreach collection="ids" item="id" separator="," open="(" close=")">
        #{id}
    </foreach>
</select>

测试类:

public void testQueryIds(){
    List<Integer> ids = new ArrayList<>();
    Collections.addAll(ids,41,43,45,52);
    QueryIds queryIds = new QueryIds();
    queryIds.setIds(ids);
    List<User> users = userDao.queryUserByIds(queryIds);
    users.forEach(user -> System.out.println(user));
}

6.png

用集合实现

数据控制层Dao

List<User> queryUserByList(List<Integer> ids);

UserDao.xml 配置文件的编写

<select id="queryUserByList" parameterType="list" resultType="user">
    select * from user where id in
    <foreach collection="collection" item="id" separator="," open="(" close=")">
        #{id}
    </foreach>
</select>

测试类:

public void testQueryByList(){
    List<Integer> ids = new ArrayList<>();
    Collections.addAll(ids,41,43,45,52);
    List<User> users = userDao.queryUserByList(ids);
    users.forEach(user -> System.out.println(user));
}

用集合实现2

数据控制层Dao

在形参前面添加一个@Param() 注解 ,里面的内容是后续UserDao.xml里面需要传入的collection值

List<User> queryUserByList(@Param("ids") List<Integer> ids);

UserDao.xml 配置文件的编写

foreach标签里面 collection的值引用的是Dao层@Param注解里面的值

<select id="queryUserByList" parameterType="list" resultType="user">
    select * from user where id in
    <foreach collection="ids" item="id" separator="," open="(" close=")">
        #{id}
    </foreach>
</select>

测试类:

public void testQueryByList(){
    List<Integer> ids = new ArrayList<>();
    Collections.addAll(ids,41,43,45,52);
    List<User> users = userDao.queryUserByList(ids);
    users.forEach(user -> System.out.println(user));
}
用数组实现

数据控制层Dao

List<User> queryUserByArray(Integer[] arr);

UserDao.xml 配置文件的编写

<select id="queryUserByArray" parameterType="int[]" resultType="user">
    select * from user where id in
    <foreach collection="array" item="id" separator="," open="(" close=")">
        #{id}
    </foreach>
</select>

测试类:

public void testQueryByArray(){
    Integer[] arr = {41,43,45,52};
    List<User> users = userDao.queryUserByArray(arr);
    users.forEach(user -> System.out.println(user));
}
用数组实现2

数据控制层Dao

在形参前面添加一个@Param() 注解 ,里面的内容是后续UserDao.xml里面需要传入的collection值

List<User> queryUserByArray(@Param("ids") Integer[] arr);

UserDao.xml 配置文件的编写

foreach标签里面 collection的值引用的是Dao层@Param注解里面的值

<select id="queryUserByArray" parameterType="int[]" resultType="user">
    select * from user where id in
    <foreach collection="ids" item="id" separator="," open="(" close=")">
        #{id}
    </foreach>
</select>

测试类:

public void testQueryByArray(){
    Integer[] arr = {41,43,45,52};
    List<User> users = userDao.queryUserByArray(arr);
    users.forEach(user -> System.out.println(user));
}
SqlMapConfig.xml的一些细节
SqlMapConfig.xml 中配置的内容和顺序

-properties(属性)

​ --property

-settings(全局配置参数)

​ --setting

-typeAliases(类型别名)

​ --typeAliase

​ --package

-typeHandlers(类型处理器)

-objectFactory(对象工厂)

-plugins(插件)

-environments(环境集合属性对象)

​ --environment(环境子属性对象)

​ ---transactionManager(事务管理)

​ ---dataSource(数据源)

-mappers(映射器)

​ --mapper

​ --package

properties标签

​ 读取properties文件内的信息

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

<environments default="mybatis">
    <environment id="mybatis">
        <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>
typeAliases标签

自定义别名方式。

配置sqlMapConfig.xml

<typeAliases>
    <typeAlias type="com.duoduo.pojo.User" alias="user"></typeAlias>
</typeAliases>

修改映射文件UserDao.xml,使用别名替换

<select id="queryAllUser" resultType="user">
    select * from user
</select>

另外一种写法

<typeAliases>
    <!--<typeAlias type="com.xq.pojo.User" alias="user"></typeAlias>-->
    <!--
		在当前包下的所有类的别名均设置为类名的小写
	-->
    <package name="com.duoduo.pojo"/>
</typeAliases>
Mappers标签

Mappers标签里面定义mapper标签。作用是用来在核心配置文件里面引入映射文件。

第一种:

<mappers>
    <mapper resource="com/duoduo/dao/UserDao.xml"></mapper>
</mappers>

第二种:

使用 mapper 接口类路径。如果我们使用注解开发的时候,就需要使用这个路径

<mappers>
    <!--路径层级之间用 / 拼接-->
	<mapper class="com.xq.dao.UserDao"></mapper>
</mappers>

第三种:

<mappers>
    <!--<mapper resource="com/duoduo/dao/UserDao.xml"></mapper>-->
    <!--路径层级之间用 . 拼接-->
    <package name="com.duoduo.dao"/>
</mappers>

动态SQL

动态查询(if标签 、where标签)

数据控制层Dao

public interface UserDao {

    List<User> queryUserByLive(User user);

    List<User> queryUserByLive2(User user);
}

UserDao.xml 配置文件的编写

<?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.duoduo.dao.UserDao">
    <select id="queryUserByLive" resultType="user" parameterType="user">
        select * from user where 1 = 1
        <if test="id != null and id != ''">
            and id = #{id}
        </if>
        <if test="username != null and username != ''">
            and username = #{username}
        </if>
        <if test="birthday != null and birthday != ''">
            and birthday = #{birthday}
        </if>
        <if test="sex != null and sex != ''">
            and sex = #{sex}
        </if>
        <if test="address != null and address != ''">
            and address = #{address}
        </if>
    </select>
    <select id="queryUserByLive2" resultType="user" parameterType="user">
        select * from user
        <!--
            where 标签
                替换了where 1 = 1
                where标签本身解析成sql语句中的where关键字
                并且在底层解析sql的时候,满足条件的第一个if标签,会将if里面的and给去掉
        -->
        <where>
            <if test="id != null and id != ''">
                and id = #{id}
            </if>
            <if test="username != null and username != ''">
                and username = #{username}
            </if>
            <if test="birthday != null and birthday != ''">
                and birthday = #{birthday}
            </if>
            <if test="sex != null and sex != ''">
                and sex = #{sex}
            </if>
            <if test="address != null and address != ''">
                and address = #{address}
            </if>
        </where>
    </select>
</mapper>

测试类:

public void testLiveQuery(){
    User user = new User();
    user.setUsername("tom");
    user.setSex("男");
    user.setAddress("广东深圳");
    List<User> users = userDao.queryUserByLive(user);
    users.forEach(u -> System.out.println(u));
}

public void testLiveQuery2(){
    User user = new User();
    user.setUsername("tom");
    user.setSex("男");
    user.setAddress("广东深圳");
    List<User> users = userDao.queryUserByLive2(user);
    users.forEach(u -> System.out.println(u));
}
总结:
where 标签
            替换了where 1 = 1
            where标签本身解析成sql语句中的where关键字
            并且在底层解析sql的时候,满足条件的第一个if标签,会将if里面的and给去掉
动态查询(where choose when otherwise 标签)

数据控制层Dao

List<User> queryUserByLive3(User user);

UserDao.xml 配置文件的编写

<select id="queryUserByLive3" resultType="user" parameterType="user">
    select * from user
    <where>
        <choose>
            <when test="username != null and username != '' ">
                username = #{username}
            </when>
            <when test="birthday != null and birthday != '' ">
                birthday = #{birthday}
            </when>
            <when test="sex != null and sex != '' ">
                sex = #{sex}
            </when>
            <when test="address != null and address != '' ">
                address = #{address}
            </when>
            <otherwise>
                id = #{id}
            </otherwise>
        </choose>
    </where>
</select>

测试类:

public void testLiveQuery3(){
    User user = new User();
    user.setUsername("tom");
    user.setSex("男");
    user.setAddress("广东深圳");
    List<User> users = userDao.queryUserByLive3(user);
    users.forEach(u -> System.out.println(u));
}

8.png

public void testLiveQuery3(){
    User user = new User();
    user.setId(52);
    List<User> users = userDao.queryUserByLive3(user);
    users.forEach(u -> System.out.println(u));
}

9.png

总结:

类似于Java语言中的

if(条件1){

​ ....

} else if(条件2){

​ ....

}else if(条件3){

​ ....

} else {

​ ....

}

choose 
	when 标签 :当条件满足当前标签,将不再向下执行
	otherwise 标签: 当前面所有when标签的条件都不满足时,执行otherwise标签的内容
动态修改(set标签)

数据控制层Dao

void updateUserByLive(User user);

UserDao.xml 配置文件的编写

<update id="updateUserByLive" parameterType="user">
    update user
    <!--
        set 标签
            set 标签自动解析成了sql语句的set关键字
            并且底层在解析set的时候,将最后一个if中的内容的逗号(,)去除,以保证sql语句执行的正确性。
    -->
    <set>
        <if test="username != null and username != ''">
            username = #{username},
        </if>
        <if test="birthday != null and birthday != ''">
            birthday = #{birthday},
        </if>
        <if test="sex != null and sex != ''">
            sex = #{sex},
        </if>
        <if test="address != null and address != ''">
            address = #{address},
        </if>
    </set>
    where id = #{id}
</update>

测试类:

public void testLiveUpdate(){
    User user = new User();
    user.setUsername("tom");
    user.setSex("男");
    user.setAddress("广东深圳");
    userDao.updateUserByLive(user);
}
动态新增(sql标签 trim标签 include标签)

数据控制层Dao

void insertUserByLive(User user);

UserDao.xml 配置文件的编写

<sql id="insertUserKey">
    <trim suffixOverrides=",">
        <if test="username != null and username != '' ">
            username,
        </if>
        <if test="birthday != null and birthday != '' ">
            birthday,
        </if>
        <if test="sex != null and sex != '' ">
            sex,
        </if>
        <if test="address != null and address != '' ">
            address,
        </if>
    </trim>
</sql>
<sql id="insertUserValue">
    <trim suffixOverrides=",">
        <if test="username != null and username != '' ">
            #{username},
        </if>
        <if test="birthday != null and birthday != '' ">
            #{birthday},
        </if>
        <if test="sex != null and sex != '' ">
            #{sex},
        </if>
        <if test="address != null and address != '' ">
            #{address},
        </if>
    </trim>
</sql>
<insert id="insertUserByLive" parameterType="user">
    insert into user(<include refid="insertUserKey"></include>) values (<include refid="insertUserValue"></include>)
</insert>

测试结果:

7.png

※批处理

新增

数据持久层Dao

void insertUsers(User user);

UserDao.xml 配置文件的编写

<insert id="insertUsers" parameterType="user">
    insert into user(username, birthday, sex, address) VALUES (#{username},#{birthday},#{sex},#{address})
</insert>

测试类:

public void testInsertUsers(){
    List<User> list = new ArrayList<>();
    User user1 = new User();
    user1.setUsername("测试数据1");
    user1.setBirthday(new Date());
    user1.setSex("N");
    user1.setAddress("测试数据1");
    User user2 = new User();
    user2.setUsername("测试数据2");
    user2.setBirthday(new Date());
    user2.setSex("N");
    user2.setAddress("测试数据2");
    User user3 = new User();
    user3.setUsername("测试数据3");
    user3.setBirthday(new Date());
    user3.setSex("N");
    user3.setAddress("测试数据3");
    Collections.addAll(list,user1,user2,user3);
    list.forEach(user -> userDao.insertUsers(user));
}

10.png

当未开启mybatis批处理的情况下,sql语句会预编译三次

开启批处理

注意!!!:开启批处理以后,要手动进行事务的提交

​ 开启方式1:

​ 在mybatis核心配置文件中添加标签 并设置参数 name="defaultExecutorType" value="BATCH"

<!--开启批处理-->
<settings>
    <setting name="defaultExecutorType" value="BATCH"/>
</settings>

​ 开启方式2:

​ 在创建SqlSession对象的时候,指定开启批处理

SqlSession session = factory.openSession(ExecutorType.BATCH,false);

11.png

多表查询

一对一查询

一个账户对应着一个用户,在Account类中维护一个属性User user

@Getter
@Setter
@ToString
public class Account {
    private Integer aid;
    private Integer uid;
    private Double money;
	//维护一个实体User属性
    private User user;
}

数据持久层Dao

List<Account> queryAccountAndUser();

UserDao.xml 配置文件的编写

一对一的实体维护:association 标签 内部属性是 javaType

<resultMap id="accountUserMap" type="account">
    <id column="aid" property="aid"></id>
    <result column="uid" property="uid"></result>
    <result column="money" property="money"></result>
    <association property="user" javaType="user">
        <id column="id" property="id"></id>
        <result column="username" property="username"></result>
        <result column="birthday" property="birthday"/>
        <result column="sex" property="sex"></result>
        <result column="address" property="address"></result>
    </association>
</resultMap>
<select id="queryAccountAndUser" resultMap="accountUserMap">
    select account.AID, account.UID, account.MONEY,user.id,user.username,user.address from account,user where UID = user.id;
</select>

测试类:

public void testQueryAccountUser(){
    List<Account> accounts = accountDao.queryAccountAndUser();
    accounts.forEach(account -> System.out.println(account));
}

一对多查询

一个用户对应多个账户,在User中添加一个List 对accounts进行维护

@Data
public class User {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    private List<Account> accounts;
}

数据持久层Dao

List<User> queryUserAndAccount();

UserDao.xml 配置文件的编写

一对多的实体维护:collection 标签,内部属性是ofType(写集合的范型)

<resultMap id="userAccountMap" type="com.duoduo.pojo.User">
    <id column="id" property="id"></id>
    <result column="username" property="username"></result>
    <result column="birthday" property="birthday"></result>
    <result column="sex" property="sex"></result>
    <result column="address" property="address"></result>
    <collection property="accounts" ofType="account">
        <id column="aid" property="aid"/>
        <result column="uid" property="uid"/>
        <result column="money" property="money"/>
    </collection>
</resultMap>
<select id="queryUserAndAccount" resultMap="userAccountMap">
    select user.id, username, birthday, sex, address,account.MONEY from user,account where account.UID = user.id;
</select>

测试类:

public void testQueryAccountUser(){
    List<User> users = userDao.queryUserAndAccount();
    users.forEach(user -> System.out.println(user));
}

多对多查询

@Data
public class Role {
    private Integer roleId;
    private String roleName;
    private String roleDesc;

    private List<User> users;
}

数据持久层Dao

List<Role> queryRoleAndUser();

UserDao.xml 配置文件的编写

和一对多好像是一样的

<resultMap id="roleMap" type="role">
    <id column="rid" property="roleId"></id>
    <result column="role_name" property="roleName"></result>
    <result column="role_desc" property="roleDesc"></result>
    <collection property="users" ofType="user">
        <id column="id" property="id"></id>
        <result column="username" property="username"></result>
        <result column="birthday" property="birthday"></result>
        <result column="sex" property="sex"></result>
        <result column="address" property="address"></result>
    </collection>
</resultMap>
<select id="queryRoleAndUser" resultMap="roleMap">
    select role.*,user.username,user.address
    from role,user_role,user
    where role.RID = user_role.RID and user.id = user_role.UID
</select>

测试类:

public void testQueryAccountUser(){
    List<Role> roles = roleDao.queryRoleAndUser();
    roles.forEach(role -> System.out.println(role));
}

Mybatis延时加载策略

Account

@Getter
@Setter
@ToString
public class Account {
    private Integer aid;
    private Integer uid;
    private Double money;

    private User user;
}

User

@Data
public class User {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
}

AccountDao

public interface AccountDao {
    List<Account> queryAllAccount();
}

UserDao

public interface UserDao {
    List<User> queryUserById(Integer id);
}

AccountDao.xml

<resultMap id="accountMap" type="account">
    <id column="aid" property="aid"></id>
    <result column="uid" property="uid"></result>
    <result column="money" property="money"></result>
    <association property="user" column="uid" select="com.duoduo.dao.UserDao.queryUserById"></association>
</resultMap>

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

UserDao.xml

<select id="queryUserById" resultType="user" parameterType="int">
    select * from user where id = #{id}
</select>

测试类:

@Test
public void testQueryAccountUser(){
    List<Account> accounts = accountDao.queryAllAccount();
    //accounts.forEach(account -> System.out.println(account));
}

12.png

执行了三条sql语句并没有延时加载的效果

延时加载配置

方式一:mybatis主要配置文件
<settings>
	<setting name="lazyLoadingEnabled" value="true"/>
	<!--
	mybatis3.4.1版本后可以不写,默认为false。
	-->
	<setting name="aggressiveLazyLoading" value="false"/>
 </settings>

方式二:AccountDao.xml文件配置

在association标签内添加一个fetchType属性 值为lazy即可做到懒加载

<resultMap id="accountMap" type="account">
    <id column="aid" property="aid"></id>
    <result column="uid" property="uid"></result>
    <result column="money" property="money"></result>
    <association property="user" column="uid" select="com.duoduo.dao.UserDao.queryUserById" fetchType="lazy"></association>
</resultMap>

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

测试类:

一定要把forEach语句注释掉

@Test
public void testQueryAccountUser(){
    List<Account> accounts = accountDao.queryAllAccount();
    //accounts.forEach(account -> System.out.println(account));
}

13.png

Mybatis缓存

  • 什么是缓存

    • 缓存就是存在内存中的数据
  • 为什么使用缓存

    • 减少和数据库交互的次数,提高执行效率

      ​ 因为数据库中的数据文件本质上其实是存储在硬盘上的(加密)文件,所以从内存中查找是要比从硬盘中查询是要块的

  • 什么样的数据适合使用缓存,什么样得数据不适合使用缓存

    • 适用:
      • 经常查询的数据,且不经常改变的数据(账号密码的存储)
      • 数据的正确与否队最终结果影响不大的
    • 不适用:
      • 经常改变的,数据的正确与否堆结果影响很大的。比如:商品的库存,银行的汇率、股市的股价。

一级缓存

他指的是mybatisSqlSession对象的缓存。当我们执行完查询之后,查询的结果会同时存在SqlSession为我们提供的一块区域中,该区域的结构是一个Map。

​ 当我们再次查询同样的结果时,mybatis会先去SqlSession中查询是否存在,存在则直接拿出来。当SqlSession对象消失时,mybatis的一级缓存也就消失了。sqlSession.close();

数据持久层Dao

User queryUserById(Integer id);

映射文件的编写UserDao.xml

<select id="queryUserById" resultType="user" parameterType="int">
    select * from user where id = #{id}
</select>

测试类:

public void testCache(){
    User user1 = userDao.queryUserById(48);
    System.out.println("The first query:" + user1);
    User user2 = userDao.queryUserById(48);
    System.out.println("The first query:" + user2);
}

14.png 这里我们可以发现,虽在我们调用了两次userDao的QueryUserById方法,但是只执行了一次数据库的交互,这就是mybatis的一级缓存起作用了,因为一级缓存的存在,所以第二次查询时直接从一级缓存中去取,没有再进行数据库的二次交互。

15.png

如果SqlSeesion执行了commit操作(更新)或者关闭,会清空缓存中的信息,避免脏读

清空缓存(sqlSqssion.close)
public void testCache(){
    User user1 = userDao.queryUserById(48);
    System.out.println("The first query:" + user1);
    session.close();	//清空缓存
    SqlSession session = factory.openSession();
    userDao = session.getMapper(UserDao.class);
    User user2 = this.userDao.queryUserById(48);
    System.out.println("The first query:" + user2);
}

16.png

数据发生更新(update)
public void testCache(){
    User user1 = userDao.queryUserById(48);
    System.out.println("The first query:" + user1);
    User user = new User();
    user.setId(48);
    user.setUsername("小爱同学");
    user.setSex("女");
    user.setBirthday(new Date());
    user.setAddress("广东深圳");
    userDao.updateUser(user);
    session.commit();
    User user2 = this.userDao.queryUserById(48);
    System.out.println("The second query:" + user2);
}

17.png

数据发生变换,update会直接抹除当前数据在缓存中的数据,避免脏读

二级缓存

sqlSessionFactory的缓存
二级缓存的开启:

pojo类必须实现Serializable接口

4.2.3.1 测试二级缓存
  • 第一步 在SqlMapConfig.xml 文件开启二级缓存
<settings>
    <!-- 开启二级缓存的支持 -->
    <setting name="cacheEnabled" value="true"/>
</settings>

因为cacheEnabled的取值默认就为true,所以这一步可以省略不配置。为true代表开启二级缓存;为false代表不开启二级缓存。

  • 第二步 配置相关的Mapper映射文件
<mapper namespace="com.xq.dao.UserDao">
<!-- 开启二级缓存的支持 -->
<cache></cache>
  • 第三步 配置statement 上面的useCache属性
<select id="findUserById" resultType="user" parameterType="int" useCache="true">
	select id,username,birthday,sex ,address  from user where id=#{id}
</select>

总结:

  • 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用
  • 如果二级缓存没有命中,再查询一级缓存
  • 如果一级缓存也没有命中,则查询数据库
  • SqlSession关闭之后,一级缓存中的数据会写入二级缓存

Mybatis注解开发

打开mybatis核心配置文件中的包映射

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

数据控制层Dao

//查询所有
@Select("select * from user")
List<User> queryAllUser();

//通过id查询
@Select("select * from user where id = #{id}")
User queryUserById(Integer id);

/**
*添加操作
*@SelectKey注解:查询表中最后新增数据的主键
*	keyProperty:实体属性的名称
*	keyColumn: 数据表对应字段的名称
*	resultType: 属性对应的数据类型
*	before: 代表查询的时机,false是在新增之后
*	statement: {查询语句}
*/
@Insert("insert into user(username, birthday, sex, address) VALUES (#{username},#{birthday},#{sex},#{address})")
@SelectKey(keyProperty = "id",keyColumn = "id",resultType = Integer.class,before = false,statement = {"select 240308"})
void insertUser(User user);

//更新操作的注解
@Update("update user set username = #{username},birthday = #{birthday},sex = #{sex},address = #{address} where id = #{id}")
void updateUser(User user);

//删除的实现
@Delete("delete from user where id = #{id}")
void deleteUserById(Integer id);

//实现聚合函数的查询
@Select("select count(id) from user")
Integer getCount();

//实现模糊查询
@Select("select * from user where username like #{username}")
List<User> queryDimByUsername(String username);

Mybatis注解解决前后字段不一致的问题

//id 为resultMap引用的id,唯一
@Results(id = "roleMap",value = {
    	//id字段默认为false ,id = true 代表当前字段是主键字段
        @Result(id = true,column = "rid",property = "rid"),
        @Result(column = "role_name",property = "roleName"),
        @Result(column = "role_desc",property = "roleDesc")
})
@Select("select * from role")
List<Role> queryAllRole();

//多次使用直接引用@Results的id
@ResultMap("roleMap")
@Select("select * from role where RID = #{id}")
Role queryRoleByRid(Integer id);

基于注解多表查询一对一查询

在Account类里面维护一个pojo实体user

@Data
public class Account {
    private Integer aid;
    private Integer uid;
    private Double money;

    private User user;
}

数据持久层Dao

public interface UserDao {

    @Select("select * from user where id = #{id}")
    User queryUserById();
}
public interface AccountDao {
    /**
     * 一对一用 one = @one 
     *      参数:select 
     *          fetchType :加载类型 FetchType.LAZY 懒加载
     * @return
     */
    @Results(id = "accountMap",value = {
            @Result(id = true,column = "aid",property = "aid"),
            @Result(column = "uid",property = "uid"),
            @Result(column = "money",property = "money"),
            @Result(column = "uid",property = "user",
                    one = @One(select = "com.duoduo.dao.UserDao.queryUserById",
                            fetchType = FetchType.LAZY)
            )
    })
    @Select("select * from account")
    List<Account> queryAllAccount();
}

基于注解多表查询一对多查询

在user类维护一个account集合

@Data
public class User {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    private List<Account> accounts;
}

数据持久层Dao

    /**
     * 一对多用 many = @Many 
     *      参数:select 
     *          fetchType :加载类型 FetchType.LAZY 懒加载
     * @return
     */
@Results(id = "userMap",value = {
        @Result(id = true,column = "id",property = "id"),
        @Result(column = "username",property = "username"),
        @Result(column = "birthday",property = "birthday"),
        @Result(column = "sex",property = "sex"),
        @Result(column = "address",property = "address"),
        @Result(column = "id",property = "accounts",
            many = @Many(select = "com.duoduo.dao.AccountDao.queryAccountByUid",
                    fetchType = FetchType.LAZY)
        )
})
@Select("select * from user")
List<User> queryAllUser();
@Select("select * from account where UID = #{uid}")
List<Account> queryAccountByUid(Integer uid);

基于注解多表查询多对多查询

在Role类维护一个user集合

@Data
public class Role {
    private Integer rid;
    private String roleName;
    private String roleDesc;

    private List<User> users;
}

数据持久层Dao

@Results(id = "roleMap",value = {
        @Result(id = true,column = "rid",property = "rid"),
        @Result(column = "role_name",property = "roleName"),
        @Result(column = "role_desc",property = "roleDesc"),
        @Result(column = "rid",property = "users",
            many = @Many(select = "com.duoduo.dao.UserDao.queryUsersById",
                    fetchType = FetchType.LAZY)
        )
})
@Select("select * from role")
List<Role> queryAllRole();
@Select("select * from user where id in(select uid from user_role where RID = #{rid})")
List<User> queryUsersById(Integer id);

Mybatis逆向工程的搭建

导入插件:

注意mysql驱动版本要和依赖中的一致

<!-- 控制Maven在构建过程中相关配置 -->
<build>
    <!-- 构建过程中用到的插件 -->
    <plugins>
        <!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
        <plugin>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-maven-plugin</artifactId>
            <version>1.3.0</version>
            <!-- 插件的依赖 -->
            <dependencies>
                <!-- 逆向工程的核心依赖 -->
                <dependency>
                    <groupId>org.mybatis.generator</groupId>
                    <artifactId>mybatis-generator-core</artifactId>
                    <version>1.3.2</version>
                </dependency>
                <!-- 数据库连接池 -->
                <dependency>
                    <groupId>com.alibaba</groupId>
                    <artifactId>druid</artifactId>
                    <version>1.1.12</version>
                </dependency>
                <!-- MySQL驱动 -->
                <dependency>
                    <groupId>mysql</groupId>
                    <artifactId>mysql-connector-java</artifactId>
                    <version>5.1.42</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
</build>
创建mybatis的核心配置文件
<?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="db.properties"></properties>

    <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>
</configuration>
创建逆向工程的配置文件

注意:逆向工程的文件名必须是:generatorConfig.xml

MyBatis3Simple: 生成基本的CRUD MyBatis3: 生成带条件的CRUD

<?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">
<generatorConfiguration>
    <!--
    targetRuntime: 执行生成的逆向工程的版本
    MyBatis3Simple: 生成基本的CRUD
    MyBatis3: 生成带条件的CRUD
    -->
    <context id="DB2Tables" targetRuntime="MyBatis3Simple">
        <!-- 数据库的连接信息 -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;characterEncoding=UTF-8&amp;useUnicode=true"
                        userId="root"
                        password="rootroot">
        </jdbcConnection>
        <!-- javaBean的生成策略-->
        <javaModelGenerator targetPackage="com.duoduo.pojo" targetProject=".\src\main\java">
            <!--
               是否生成子包。如果为true com.xq.pojo生成的保姆那个带有层级目录
               false  com.xq.pojo就是一个包名
            -->
            <property name="enableSubPackages" value="true" />
            <!--
               通过数据表字段生成pojo。如果字段名称带空格,会去掉空格
            -->
            <property name="trimStrings" value="true" />
        </javaModelGenerator>
        <!-- SQL映射文件的生成策略 -->
        <sqlMapGenerator targetPackage="com.duoduo.mapper"
                         targetProject=".\src\main\resources">
            <property name="enableSubPackages" value="true" />
        </sqlMapGenerator>
        <!-- Mapper接口的生成策略 -->
        <javaClientGenerator type="XMLMAPPER"
                             targetPackage="com.duoduo.mapper" targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true" />
        </javaClientGenerator>
        <!-- 逆向分析的表 -->
        <!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
        <!-- domainObjectName属性指定生成出来的实体类的类名 -->
        <table tableName="user" domainObjectName="User"/>
    </context>
</generatorConfiguration>

18.png

执行后的效果

19.png

Mybatis分页插件

导入依赖

<!--分页插件-->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.2.0</version>
</dependency>

在mybatis核心配置文件中导入插件

<!--配置分页插件-->
<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>

测试类:

@Test
public void test03() throws Exception{
    //开启分页操作
    PageHelper.startPage(2,4);
    List<User> userList = userMapper.selectByExample(null);
    //创建分页模型对象
    PageInfo<User> pageInfo = new PageInfo<>(userList);
    System.out.println(pageInfo);
}

属性解读:

  • pageNum:当前页的页码
  • pageSize:每页显示的条数
  • size:当前页显示的真实条数
  • total:总记录数
  • pages:总页数
  • prePage:上一页的页码
  • nextPage:下一页的页码
  • isFirstPage/isLastPage:是否为第一页/最后一页
  • hasPreviousPage/hasNextPage:是否存在上一页/下一页
  • navigatePages:导航分页的页码数
  • navigatepageNums:导航分页的页码,[1,2,3,4,5]