《MyBatis》学习笔记

一、:tangerine:MyBatis简介:tangerine:

  • MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。
  • MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
  • MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
  • MyBatis 是一款轻量级的、ORM的框架

二、第一个MyBatis程序:yellow_heart:

2.1 搭建环境

  • 数据库 gokudb
  • 所需Jar包
<!-- mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.6</version>
</dependency>

<!--junit-->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

<!--mysql-connector-java-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.22</version>
</dependency>

2.2 mybatis-config.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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/goku?serverTimezone=Asia/Shanghai"/>
                <property name="username" value="root"/>
                <property name="password" value="795200"/>
            </dataSource>
        </environment>
    </environments>
    <!--每一个mapper.xml都需要在MyBatis核心配置文件中注册-->
    <mappers>
        <mapper resource="com/singerw/mapper/UserMapper.xml"/>
    </mappers>
</configuration>

2.3 编写MyBatisUtils工具类

/**
 * @Author: CodeSleep
 * @Date: 2021/8/23 21:15
 * @Description: //TODO MyBatis工具类
 */
public class MyBatisUtils {
    private static SqlSessionFactory sqlSessionFactory;

    // 加载资源
    static {
        try {
            // 使用MyBatis获取SqlSessionFactory对象
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * @return
     * @Author CodeSleep
     * @Date: 2021/8/23 21:13
     * @Description: //TODO 创建执行SQL的对象
     * sqlSessionFactory中获取SqlSession实例
     * SqlSession完全包含了面向数据库执行SQL命令所需的所有方法;
     */
    public static SqlSession getSqlSession() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        return sqlSession;
    }
}

2.4 编写代码

  • 实体类(User.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int userid;
    private String username;
    private String userphone;
    private String userpassword;
    private int jurisdiction;
    private String createtime;
    private String logintime;
    private int userstatus;
}
  • Dao数据持久层接口(UserMapper.java
public interface UserMapper {

    List<User> getUsers(int userid);
}
  • Dao数据持久层(UserMapper.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.singerw.mapper.UserMapper">
    <select id="getUsers" resultType="com.singerw.pojo.User">
        select * from goku.g_users where userid = #{userid}
    </select>
</mapper>
  • 测试类(UserMapperTest.java
public class UserMapperTest {

    @Test
    public void getUsers() {
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = userMapper.getUsers(1);
        for (User user : users) {
            System.out.println(user);
        }
        sqlSession.close();
    }
}

2.5 注意点

pom.xmlbuild中配置resources,来防止我们资源在Maven项目中的导出失败的问题!

<!--在build中配置resources,来防止我们资源导出失败的问题-->
<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
        </resource>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
        </resource>
    </resources>
</build>

可能会遇到的问题:

  1. 配置文件没有注册
  2. 绑定接口错误
  3. 方法名不对
  4. 返回类型不对
  5. Maven导出资源问题

三、生命周期和作用域

作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。

  • SqlSessionFactoryBuilder

    • 局部变量
    • 这个类可以被实例化、使用和丢弃,它一旦创建了 SqlSessionFactory,就不再需要它了。
  • SqlSessionFactory

    • SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
    • 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。
    • 因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
    • 可以想象为:数据库连接池
  • SqlSession

    • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
    • 用完之后需要赶紧关闭,否则资源将被占用!
    • 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:

这里面的每一个Mapper都代表一个具体的业务。

四、配置文件之基本优化:tanabata_tree:

MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:

3.1 mybatis-config.xml配置文件属性优化

mybatis-config.xml中拥有数据库的账号密码,安全起见,不能直接加入到编译文件中,新建一个db.properties,将驱动、URL、账号、密码等数据存储到这里,然后在mybatis-config.xml调用即可。

<!--加载properties文件到MyBatis配置文件中-->
<properties resource="db.properties"></properties>

【示例】:mybatis-config.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>
    <!--加载properties文件到MyBatis配置文件中-->
    <properties resource="db.properties"></properties>
    
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <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>
    <!--每一个mapper.xml都需要在MyBatis核心配置文件中注册-->
    <mappers>
        <mapper resource="com/singerw/mapper/UserMapper.xml"/>
        <mapper resource="com/singerw/mapper/ArticleMapper.xml"/>
    </mappers>

</configuration>

【示例】:db.properties

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/goku?serverTimezone=Asia/Shanghai
username=root
password=795200

3.2 类型别名优化

​ 在Mapper XML中使用实体类时候,每一次都需要从com.singerw.pojo.XXX中加载拿取使用,例如resultType="Article"的等等,为方便起见,直接在取别名在mybatis-config.xml文件中给该实体类取别名,方便使用,也有其他的作用。

:a:方案一、DIY别名

【示例】mybatis-config.xml

<!-- 别名 -->
    <typeAliases>
    <typeAlias type="com.singerw.pojo.User" alias="User" />
    <typeAlias type="com.singerw.pojo.Article" alias="Article" />
    </typeAliases>

【示例】基本使用

<mapper namespace="com.singerw.mapper.ArticleMapper">
    <!--查询所有文章-->
    <select id="getArtilceList" resultType="Article">
        select *
        from artilce;
    </select>
</mapper>

:b:方案二、扫描包别名

也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

<typeAliases>
    <package name="com.singerw.pojo"/>
</typeAliases>

会扫描实体类的包,它的默认别名就为这个类的类名,首字母小写!

  • 实体类比较少的时候建议使用第一种。
  • 实体类比较多的时候建议使用第二种。

五、MyBatis实现CRUD:sailboat:

4.1 XML形式的CURD

1、增加操作

:a:方案一、通过对象插入

示例:UserMapper.java

public interface UserMapper {

    boolean addUser(User user);
}

示例:UserMapper.xml

<mapper namespace="com.singerw.mapper.UserMapper">

    <!--增加用户-->
    <insert id="addUser">
        insert into g_users
        value (null,
        #{username},
        #{userphone},
        #{userpassword},
        0,
        now(),
        now(),
        1)
    </insert>

</mapper>

示例:UserMapperTest.java

public class UserMapperTest {

    private SqlSession sqlSession = MyBatisUtils.getSqlSession();
    private UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    @Test
    public void addUser() {
        User user = new User("张欣", "19999999999", "assam1314520");
        boolean flag = userMapper.addUser(user);
        sqlSession.commit();
        System.out.println(flag ? "增加成功" : "增加失败");
    }

}
:b:方案二、通过Map插入

:yellow_heart:(万能方法)工作必备野路子方法,也可以使用到查询,修改,删除等等中:yellow_heart:

假设我们的实体类中,或者数据库的表中,字段或者参数很多,我们应当考虑使用Map插入

示例:UserMapper.java

public interface UserMapper {

    boolean addUser2(Map<String,Object> map);
}

示例:UserMapper.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.singerw.mapper.UserMapper">

    <!--增加用户-->
    <insert id="addUser2" parameterType="map">
        insert into g_users (userid, username, userphone, userpassword, jurisdiction, createtime, logintime, userstatus)
        values (null,#{username},#{userphone},#{userpassword},#{jurisdiction},now(),now(),#{userstatus});
    </insert>

</mapper>

示例:UserMapperTest.java

public class UserMapperTest {

    private SqlSession sqlSession = MyBatisUtils.getSqlSession();
    private UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    @Test
    public void addUser() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        HashMap hashMap = new HashMap<String,Object>();
        hashMap.put("username","singerw");
        hashMap.put("userphone","18888888888");
        hashMap.put("userpassword","123456");
        hashMap.put("jurisdiction",1);
        hashMap.put("userstatus",1);
        mapper.addUser2(hashMap);
    }
}

2、删除操作

示例:UserMapper.java

public interface UserMapper {

    boolean delUser(int userid);
}

示例:UserMapper.xml

<mapper namespace="com.singerw.mapper.UserMapper">

    <!--根据ID删除用户-->
    <delete id="delUser">
        delete
        from goku.g_users
        where userid = #{userid}
    </delete>
    
</mapper>

示例:UserMapperTest.java

public class UserMapperTest {

    private SqlSession sqlSession = MyBatisUtils.getSqlSession();
    private UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    @Test
    public void delUser() {
        boolean flag = userMapper.delUser(10);
        //必须要提交
        sqlSession.commit();
        System.out.println(flag ? "删除成功" : "删除失败");
        sqlSession.close();
    }
}

3、修改操作

:a:方案一、参数为一个对象

示例:UserMapper.java

public interface UserMapper {

    boolean updateUser2(User user);
}

示例:UserMapper.xml

<mapper namespace="com.singerw.mapper.UserMapper">

    <!--根据ID修改用户-->
    <update id="updateUser">
        update g_users
        set username     = #{username},
        userphone    = #{userphone},
        userpassword = #{userpassword},
        jurisdiction = #{jurisdiction},
        userstatus   = #{userstatus}
        where userid = #{userid}
    </update>

</mapper>

示例:UserMapperTest.java

public class UserMapperTest {

    private SqlSession sqlSession = MyBatisUtils.getSqlSession();
    private UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    @Test
    public void updateUser2() {
        User user = new User("测试测试测试", "156515561561", "123456", 1, 1, 8);
        boolean flag = userMapper.updateUser2(user);
        //必须要提交
        sqlSession.commit();
        System.out.println(flag ? "修改成功" : "修改失败");
    }
}
:b:方案二、参数为多个不同参数

注意:此时我们的修改的方法的参数不再是单一的参数,而是多个参数

示例:UserMapper.java

public interface UserMapper {

    boolean updateUser(String username, String userphone, String userpassword, int jurisdiction, int userstatus, int userid);
}

示例:UserMapper.xml

<mapper namespace="com.singerw.mapper.UserMapper">

    <!--根据ID修改用户-->
    <update id="updateUser">
        update g_users
        set username     = #{username},
        userphone    = #{userphone},
        userpassword = #{userpassword},
        jurisdiction = #{jurisdiction},
        userstatus   = #{userstatus}
        where userid = #{userid}
    </update>

</mapper>

示例:UserMapperTest.java

public class UserMapperTest {

    private SqlSession sqlSession = MyBatisUtils.getSqlSession();
    private UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    @Test
    public void updateUser() {
        boolean flag = userMapper.updateUser("测试测试测试", "1444444444", "123456", 1, 1, 5);
        //必须要提交
        sqlSession.commit();
        System.out.println(flag ? "修改成功" : "修改失败");
    }
}

报错处理

这时我们发现测试结果报错:

Parameter 'username' not found. Available parameters are [arg3, arg2, param5, arg5, arg4, param6, arg1, arg0, param3, param4, param1, param2]

多个参数的绑定默认按照顺序;

:a:解决方法一(推荐):修改UserMapper.xml中的update参数信息为如下,MyBatis才能识别到。

解决方法示例:UserMapper.xml

<mapper namespace="com.singerw.mapper.UserMapper">

    <!--根据ID修改用户-->
    <update id="updateUser">
        update g_users
        set username     = #{arg0},
        userphone    = #{arg1},
        userpassword = #{arg2},
        jurisdiction = #{arg3},
        userstatus   = #{arg4}
        where userid = #{arg5}
    </update>

</mapper>

或者修改UserMapper.java中的updateUser方法的参数信息,这样MyBatis也能识别到。

public interface UserMapper {

    boolean updateUser(@Param("username") String username, @Param("userphone") String userphone, @Param("userpassword") String userpassword, @Param("jurisdiction") int jurisdiction, @Param("userstatus") int userstatus, @Param("userid") int userid);

}

:b:解决方法二:将参数构造成一个map对象,将我们的属性传递进来

解决方法示例:UserMapper.java

public interface UserMapper {

    boolean updateUser(Map map);
}

解决方法示例:UserMapper.xml

<mapper namespace="com.singerw.mapper.UserMapper">
    <!--根据ID修改用户-->
    <!--根据ID修改用户-->
    <!--根据ID修改用户-->
    <update id="updateUser">
        update g_users
        set username     = #{username},
        userphone    = #{userphone},
        userpassword = #{userpassword},
        jurisdiction = #{jurisdiction},
        userstatus   = #{userstatus}
        where userid = #{userid}
    </update>
    
</mapper>

解决方法示例:UserMapperTest.java

public class UserMapperTest {

    private SqlSession sqlSession = MyBatisUtils.getSqlSession();
    private UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    @Test
    public void updateUser() {
        HashMap map = new HashMap();
        map.put("username","测试测试测试");
        map.put("userphone","1444444444");
        map.put("userpassword","123456");
        map.put("jurisdiction",1);
        map.put("userstatus",0);
        map.put("userid",10);

        boolean flag = userMapper.updateUser(map);  
        //必须要提交
        sqlSession.commit();
        System.out.println(flag ? "修改成功" : "修改失败");
    }
}

4 、查询操作

示例:UserMapper.java

public interface UserMapper { 

    List<User> getUsers();

    User getUser(int userid);
}

示例:UserMapper.xml

<mapper namespace="com.singerw.mapper.UserMapper">
    <!--查询所有用户-->
    <!-- 此时没有指定包的情况,其实用的是别名Blog -->
    <select id="getUsers" resultType="User">
        select *
        from goku.g_users
    </select>

    <!-- 此时没有指定包的情况,其实用的是别名Blog -->
    <select id="getUser" resultType="User">
        select *
        from goku.g_users
        where userid = #{userid}
    </select>

</mapper>

示例:UserMapperTest.java

public class UserMapperTest {

    private SqlSession sqlSession = MyBatisUtils.getSqlSession();
    private UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    @Test
    public void getUsers() {
        List<User> users = userMapper.getUsers();
        for (User user : users) {
            System.out.println(user);
        }
        sqlSession.close();
    }

    @Test
    public void getUser() {
        User user = userMapper.getUser(2);
        System.out.println(user);
        sqlSession.close();
    }
}

4.2 Annotation注解形式的CURD

1、增加操作

示例:UserMapper.java

public interface UserMapper {

    @Insert(value = "INSERT INTO g_users VALUE (NULL,#{username},#{userphone},#{userpassword},0,now(),now(),1)")
    boolean addUser(User user);

}

示例:UserMapperTest.java

public class UserMapperTest {

    private SqlSession sqlSession = MyBatisUtils.getSqlSession();
    private UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    @Test
    public void addUser() {
        User user = new User("张欣", "5465432543453", "assam1314520");
        boolean flag = userMapper.addUser(user);
        sqlSession.commit();
        System.out.println(flag ? "增加成功" : "增加失败");
    }
}

2、删除操作

示例:UserMapper.java

public interface UserMapper {

    @Delete(value = "DELETE FROM goku.g_users WHERE userid=#{userid}")
    boolean delUser(int userid);
}

示例:UserMapperTest.java

public class UserMapperTest {

    private SqlSession sqlSession = MyBatisUtils.getSqlSession();
    private UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    @Test
    public void delUser() {
        boolean flag = userMapper.delUser(15);
        //必须要提交
        sqlSession.commit();
        System.out.println(flag ? "删除成功" : "删除失败");
        sqlSession.close();
    }
}

3、修改操作

示例:UserMapper.java

public interface UserMapper {

    @Update(value = "UPDATE g_users SET username=#{username},userphone=#{userphone},userpassword=#{userpassword},jurisdiction=#{jurisdiction},userstatus=#{userstatus} WHERE userid=#{userid}")
    boolean updateUser(@Param("username") String username, @Param("userphone") String userphone, @Param("userpassword") String userpassword, @Param("jurisdiction") int jurisdiction, @Param("userstatus") int userstatus, @Param("userid") int userid);


}

示例:UserMapperTest.java

public class UserMapperTest {

    private SqlSession sqlSession = MyBatisUtils.getSqlSession();
    private UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    @Test
    public void updateUser() {
        boolean flag = userMapper.updateUser("测试测试测试", "1444444444", "123456", 1, 1, 5);
        //必须要提交
        sqlSession.commit();
        System.out.println(flag ? "修改成功" : "修改失败");
    }
}

4、查询操作

示例:UserMapper.java

public interface UserMapper {
    
    @Select(value = "select * from goku.g_users")
    List<User> getUsers();

    @Select(value = "select * from goku.g_users where userid = #{userid}")
    User getUser(int userid);
}

示例:UserMapperTest.java

public class UserMapperTest {

    private SqlSession sqlSession = MyBatisUtils.getSqlSession();
    private UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    @Test
    public void getUsers() {
        List<User> users = userMapper.getUsers();
        for (User user : users) {
            System.out.println(user);
        }
        sqlSession.close();
    }

    @Test
    public void getUser() {
        User user = userMapper.getUser(2);
        System.out.println(user);
        sqlSession.close();
    }
}

4.3 万能Map:ocean:

:yellow_heart:(万能方法)工作必备野路子方法,也可以使用到查询,修改,删除等等中:yellow_heart:

假设我们的实体类中,或者数据库的表中,字段或者参数很多,我们应当考虑使用Map插入

示例:UserMapper.java

public interface UserMapper {

    boolean addUser2(Map<String,Object> map);
}

示例:UserMapper.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.singerw.mapper.UserMapper">

    <!--增加用户-->
    <insert id="addUser2" parameterType="map">
        insert into g_users (userid, username, userphone, userpassword, jurisdiction, createtime, logintime, userstatus)
        values (null,#{username},#{userphone},#{userpassword},#{jurisdiction},now(),now(),#{userstatus});
    </insert>

</mapper>

示例:UserMapperTest.java

public class UserMapperTest {

    private SqlSession sqlSession = MyBatisUtils.getSqlSession();
    private UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    @Test
    public void addUser() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        HashMap hashMap = new HashMap<String,Object>();
        hashMap.put("username","singerw");
        hashMap.put("userphone","18888888888");
        hashMap.put("userpassword","123456");
        hashMap.put("jurisdiction",1);
        hashMap.put("userstatus",1);
        mapper.addUser2(hashMap);
    }
}

六、Mapper XML 文件:dart:

​ MyBatis 的真正强大在于它的映射语句,也是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 就是针对 SQL 构建的,并且比普通的方法做的更好。

SQL 映射文件有很少的几个顶级元素(按照它们应该被定义的顺序):

  • cache – 给定命名空间的缓存配置。
  • cache-ref – 其他命名空间缓存配置的引用。
  • resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
  • parameterMap – 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。
  • sql – 可被其他语句引用的可重用语句块。
  • insert – 映射插入语句
  • update – 映射更新语句
  • delete – 映射删除语句
  • select – 映射查询语句
属性描述
keyPropertyselectKey 语句结果应该被设置的目标属性。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
keyColumn匹配属性的返回结果集中的列名称。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
resultType结果的类型。MyBatis 通常可以推算出来,但是为了更加确定写上也不会有什么问题。MyBatis 允许任何简单类型用作主键的类型,包括字符串。如果希望作用于多个生成的列,则可以使用一个包含期望属性的 Object 或一个 Map。
order这可以被设置为 BEFORE 或 AFTER。如果设置为 BEFORE,那么它会首先选择主键,设置 keyProperty 然后执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 元素 - 这和像 Oracle 的数据库相似,在插入语句内部可能有嵌入索引调用。
statementType与前面相同,MyBatis 支持 STATEMENT,PREPARED 和 CALLABLE 语句的映射类型,分别代表 PreparedStatement 和 CallableStatement 类型。

七、ResultMat的使用-xml:ocean:

  • resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。
  • 实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同等功能的数千行代码。
  • ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。

6.1 简单结果集映射

解决属性名和字段名不一致的问题

【示例】数据库中的User表字段

  • userid
  • username
  • userphone
  • userpassword
  • jurisdiction
  • createtime
  • logintime
  • userstatus

数据库中的字段和实体类中的字段不一致。

【示例】User.java

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    private int id;
    private String name;
    private String phone;
    private String password;
    private int juri;
    private String created;
    private String logined;
    private int status;
}

使用结果集映射

【示例】UserMapper.xml

<mapper namespace="com.singerw.mapper.UserMapper">
    <!--查询所有用户-->
    <select id="getUsers" resultMap="userMap">
        select *
        from goku.g_users
    </select>

    <!--根据ID查询用户-->
    <select id="getUser" resultMap="userMap">
        select *
        from goku.g_users
        where userid = #{userid}
    </select>

    <!--resultMap结果集映射-->
    <resultMap id="userMap" type="User">
        <!-- 实体类和表列做关联-->
        <!-- id 主键 属性 -->
        <id property="id" column="userid"></id>
        <!--其他部分-->
        <!--column数据库中的字段,property实体类中的属性-->
        <result property="name" column="username"></result>
        <result property="phone" column="userphone"></result>
        <result property="password" column="userpassword"></result>
        <result property="jurisdiction" column="jurisdiction"></result>
        <result property="juri" column="createtime"></result>
        <result property="created" column="createtime"></result>
        <result property="logined" column="logintime"></result>
        <result property="status" column="userstatus"></result>
    </resultMap>

</mapper>

【示例】UserMapperTest.java

public class UserMapperTest {

    private SqlSession sqlSession = MyBatisUtils.getSqlSession();
    private UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    @Test
    public void getUsers() {
        List<User> users = userMapper.getUsers();
        for (User user : users) {
            System.out.println(user);
        }
        sqlSession.close();
    }

    @Test
    public void getUser() {
        User user = userMapper.getUser(2);
        System.out.println(user);
        sqlSession.close();
    }

}

6.3 Association完整案例

一个复杂类型的关联,按照结果嵌套查询,多对一

:ocean:(以1:N为案例关联映射):ocean:

​ 在数据库设计中,普遍存在一对多,多对一的表关系。blogdb数据库中三个基本表结构如下 。

在数据库表设计的过程中,建立了如上的表结构,那么到程序设计中,实体类应该怎么写呢?

一个作者可以发表多篇文章,而发表的这些多篇文章只能有一个作者,这里文章和作者是1:N的关系。

Article(N):如果需要得到一篇或多篇文章的作者,需要在实体类中增加一个类型。

private Users users

User(1):在user表中查询某一用户的时候,同时想得到这个用户发表了哪些文章,需要在实体类中增加一个集合。

private List<Article> articleList

通过关键字或者文章ID查询一篇或多篇文章和每篇文章的作者信息

步骤一、编写实体类

【实体类示例】:Article.java

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Article {
    private int id;
    private String title;
    private String content;
    private String summary;
    private int cid;
    private Users users;
    private String created;
    private int status;
}

【实体类示例】:Users.java

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Users {
    private int id;
    private String username;
    private String nickname;
    private String password;
    private int status;
    private String email;
    private String userface;
    private String created;
    private String lastlogin;
    private List<Article> articleList;
}

步骤二、实现数据查询操作

【SQL语句示例】

<!--使用了resultMap,需要在xml中创建一个id为articleMap的resultMap-->
<select id="getArticles" resultMap="articleMap">
        SELECT article.id,
               article.title,
               article.content,
               article.summary,
               article.created,
               article.`status`,
               `user`.id AS uid,
               `user`.username,
               `user`.nickname
        FROM article
                 INNER JOIN `user` ON article.uid = `user`.id
        WHERE article.title LIKE #{title}
    </select>

步骤三、编写查询方法接口

【查询方法示例】:ArticleMapper.java

public interface ArticleMapper {

    List<Article> getArticles(String title);
}

步骤四、编写mapper.xml中的ResultMap

【ResultMap关联示例】:ArticleMapper.xml

<!-- 映射关系的管理和定义 ,type指的是实体类型(取了别名),id当前的resultMap的名字 -->
<resultMap type="Article" id="articleMap">
    <!-- 主键使用id -->
    <id property="id" column="id"/>
    <!-- 一个result表示一个列和实体类的属性的对应关系 -->
    <result property="title" column="title"/>
    <result property="content" column="content"/>
    <result property="summary" column="summary"/>
    <result property="created" column="created"/>
    <result property="status" column="status"/>
    <!-- 此时我们的Article类中,增加了一个Users类型的属性 -->
    <association property="users" column="uid" javaType="com.singerw.pojo.Users">
        <!-- author类对应的表中的列 和 类属性关联 -->
        <id property="id" column="uid"/>
        <result property="username" column="username"/>
        <result property="nickname" column="nickname"/>
    </association>
</resultMap>

步骤五、进行单元测试

public class ArticleMapperTest {

    @Test
    public void getArticles() {
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        ArticleMapper mapper = sqlSession.getMapper(ArticleMapper.class);
        List<Article> articles = mapper.getArticles("%VUE%");
        articles.forEach(System.out::println);
    }
}

Article(id=2, title=VUE, content=VUE是极简的前端框架, summary=vue, category=null, users=Users(id=1, username=tom, nickname=唐木松, password=null, status=0, email=null, userface=null, created=null, lastlogin=null, articleList=null, categoriesList=null), created=2020-12-25 01:20:20, status=1)

6.4 Collection节点案例

一个复杂类型的集合。按照结果嵌套查询,一对多

:ocean:(以1:N为案例关联映射):ocean:

​ 在数据库设计中,普遍存在一对多,多对一的表关系。blogdb数据库中三个基本表结构如下 。

在数据库表设计的过程中,建立了如上的表结构,那么到程序设计中,实体类应该怎么写呢?

一个作者可以发表多篇文章,而发表的这些多篇文章只能有一个作者,这里文章和作者是1:N的关系。

Article(N):如果需要得到一篇或多篇文章的作者,需要在实体类中增加一个类型。

private Users users

User(1):在user表中查询某一用户的时候,同时想得到这个用户发表了哪些文章,需要在实体类中增加一个集合。

private List<Article> articleList

通过用户ID查询用户和用户发表的文章

步骤一、编写实体类

【实体类示例】:Article.java

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Article {
    private int id;
    private String title;
    private String content;
    private String summary;
    private int cid;
    private Users users;
    private String created;
    private int status;
}

【实体类示例】:Users.java

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Users {
    private int id;
    private String username;
    private String nickname;
    private String password;
    private int status;
    private String email;
    private String userface;
    private String created;
    private String lastlogin;
    private List<Article> articleList;
    private List<Category> categoriesList;
}

步骤二、实现数据查询操作

【SQL语句示例】

SELECT `user`.id       AS userid,
               `user`.username,
               `user`.nickname,
               `user`.`password`,
               `user`.`status` AS userstatus,
               `user`.email,
               `user`.userface,
               `user`.created  AS user_created,
               `user`.lastlogin,
               article.id      AS articleid,
               article.title,
               article.content,
               article.summary,
               article.created,
               article.STATUS  AS articlestatus
        FROM `user`
                 INNER JOIN article ON `user`.id = article.uid
        WHERE `user`.id = #{id}

步骤三、编写查询方法接口

【查询方法示例】:UsersMapper.java

public interface UsersMapper {

    <List>Users getUserAndArticle(int id);
}

步骤四、编写mapper.xml中的ResultMap

【ResultMap关联示例】:UsersMapper.xml

<!-- 映射关系的管理和定义 ,type指的是实体类型,id当前的resultMap的名字 -->
<resultMap type="Users" id="userMap2">
    <!-- 主键使用id -->
    <id property="id" column="userid"/>
    <!-- 一个result表示一个列和实体类的属性的对应关系 -->
    <result property="username" column="username"/>
    <result property="nickname" column="nickname"/>
    <result property="password" column="password"/>
    <result property="status" column="userstatus"/>
    <result property="email" column="email"/>
    <result property="userface" column="userface"/>
    <result property="created" column="user_created"/>
    <result property="lastlogin" column="lastlogin"/>

    <collection property="articleList" ofType="Article">
        <id property="id" column="articleid"/>
        <result property="title" column="title"/>
        <result property="content" column="content"/>
        <result property="summary" column="summary"/>
        <result property="created" column="category_created"/>
        <result property="status" column="articlestatus"/>
    </collection>
</resultMap>

步骤五、进行单元测试

public class UsersMapperTest {

    @Test
    public void getUserAndArticle() {
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);
        Users user = mapper.getUserAndArticle(2);
        System.out.println(user);
    }
}

Users(id=2, username=jerry, nickname=吴八阁, password=e10adc3949ba59abbe56e057f20f883e, status=1, email=jerry@qq.com, userface=jerry.jpg, created=2021-01-08 01:01:22, lastlogin=2020-12-08 01:01:22, articleList=[Article(id=1, title=SpringBoot, content=SpringBoot是spring全家桶中很重要的一员, summary=spring, category=null, users=null, created=null, status=1), Article(id=5, title=MySQL, content=VUE是极简的前端框架, summary=vue, category=null, users=null, created=null, status=1), Article(id=7, title=Dubbo, content=VUE是极简的前端框架, summary=vue, category=null, users=null, created=null, status=1), Article(id=8, title=RabbitMQ, content=VUE是极简的前端框架, summary=vue, category=null, users=null, created=null, status=1), Article(id=10, title=Oracle, content=VUE是极简的前端框架, summary=vue, category=null, users=null, created=null, status=1), Article(id=12, title=Layui, content=VUE是极简的前端框架, summary=vue, category=null, users=null, created=null, status=1)], categoriesList=null)

6.5 ResultMap使用常见问题

1、使用别名时,要创建别名

<resultMap type="Users" id="userMap2">
<!-- 别名 -->
<typeAliases>
    <typeAlias type="com.singerw.pojo.Users" alias="Users" />
</typeAliases>

2、使用resultMap时需要先创建一个resultMap

<select id="getCategoryAndArticle" resultMap="categoryMap">
    ...
</select>
<resultMap id="categoryMap" type="Category">
   ...
</resultMap>

3、resultMap中的每张表都要求都有主键

 <id property="id" column="id"/>
<resultMap id="categoryMap" type="Category">
    <!--主键-->
    <id property="id" column="id"/>
    <!--其他部分-->
    <result property="catename" column="catename"/>
    ...
    <collection property="articleList" ofType="Article">
        <result property="id" column="art_id"/>
       ...
    </collection>
</resultMap>

4、resultproperty需要实体类中的属性名,column是表中的类名。

<result property="id" column="art_id"/>

5、JavaType:是用来指定实体类中属性的类型。

6、ofType:是用来指定映射到List或者集合中的实体类类型,泛型中的约束类型!

八、 ResultMap的使用-annotation

8.1 简单结果集映射使用

【示例】实体类Article.java

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Article implements Serializable {
    private int id;
    private String artTitle;
    private String artContent;
    private String artSummary;
    private String artCreated;
    private int artStatus;
}

【示例】mybatis-config.xml

<mappers>
    <mapper class="com.singerw.mapper.ArticleMapper"/>
</mappers>

【示例】mapper.java接口

public interface ArticleMapper {
    @Results(id = "articleResults",value = {
        @Result(property = "id",column = "id",id = true),
        @Result(property = "artTitle",column = "title"),
        @Result(property = "artContent",column = "content"),
        @Result(property = "artSummary",column = "summary"),
        @Result(property = "artStatus",column = "status"),
    })
    @Select(value = "select * from article where id = #{id}")
    Article getArticleByID(@Param("id") int id);
}

上方定义的Results也可以引用到别的方法上。如下:

public interface ArticleMapper {
   
    //这里的ResultMap是引用在注解中已经定义好的某个Results的ID
    @ResultMap(value = "articleResults")
    @Select(value = "select * from article")
    List<Article> getAllArticle();
}

8.2 高级结果集映射

八、日志:zap:

7.1 日志工厂

Mybatis内置的日志工厂提供日志功能,具体的日志实现有以下几种工具:

  • SLF4J
  • Apache Commons Logging
  • Log4j 2
  • Log4j
  • JDK logging

【示例】mybatis-config.xml中配置setting

<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

7.2 Log4j的基本使用

​ Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

步骤一:导入Jar文件

<!--log4j-->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.16</version>
</dependency>

步骤二:配置log4j配置文件

新建log4j.properties,进行配置

log4j.rootLogger=DEBUG, console, file
#控制台输出的相关设置
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Target=System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [%p][%c{1}] - %m%n
log4j.appender.console.Encoding=UTF-8

#文件输出的相关设置
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./logs/log.log
log4j.appender.file.MaxFileSize=500KB
log4j.appender.file.MaxBackupIndex=1
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [%p]-%c{1} - %m%n
log4j.appender.file.encoding=UTF-8

#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
  1. log4j.rootCategory=INFO, stdout , R

此句为将等级为INFO的日志信息输出到stdout和R这两个目的地,stdout和R的定义在下面的代码,可以任意起名。等级可分为OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL,如果配置OFF则不打出任何信息,如果配置为INFO这样只显示INFO、WARN、ERROR的log信息,而DEBUG信息不会被显示,具体讲解可参照第三部分定义配置文件中的logger。

  1. log4j.appender.stdout=org.apache.log4j.ConsoleAppender

此句为定义名为stdout的输出端是哪种类型,可以是

org.apache.log4j.ConsoleAppender(控制台),

org.apache.log4j.FileAppender(文件),

org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件),

org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)

org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)

具体讲解可参照第三部分定义配置文件中的Appender。

  1. log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

此句为定义名为stdout的输出端的layout是哪种类型,可以是

org.apache.log4j.HTMLLayout(以HTML表格形式布局),

org.apache.log4j.PatternLayout(可以灵活地指定布局模式),

org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串),

org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)

具体讲解可参照第三部分定义配置文件中的Layout。

  1. log4j.appender.stdout.layout.ConversionPattern= [QC] %p [%t] %C.%M(%L) | %m%n

如果使用pattern布局就要指定的打印信息的具体格式ConversionPattern,打印参数如下:

%m 输出代码中指定的消息;

%M 输出打印该条日志的方法名;

%p 输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL;

%r 输出自应用启动到输出该log信息耗费的毫秒数;

%c 输出所属的类目,通常就是所在类的全名;

%t 输出产生该日志事件的线程名;

%n 输出一个回车换行符,Windows平台为"rn”,Unix平台为"n”;

%d 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyyy-MM-dd HH:mm:ss,SSS},输出类似:2002-10-18 22:10:28,921;

%l 输出日志事件的发生位置,及在代码中的行数;

[QC]是log信息的开头,可以为任意字符,一般为项目简称。

输出的信息

[TS] DEBUG [main] AbstractBeanFactory.getBean(189) | Returning cached instance of singleton bean 'MyAutoProxy'

具体讲解可参照第三部分定义配置文件中的格式化日志信息。

  1. log4j.appender.R=org.apache.log4j.DailyRollingFileAppender

此句与第3行一样。定义名为R的输出端的类型为每天产生一个日志文件。

  1. log4j.appender.R.File=D:\Tomcat 5.5\logs\qc.log

此句为定义名为R的输出端的文件名为D:\Tomcat 5.5\logs\qc.log可以自行修改。

  1. log4j.appender.R.layout=org.apache.log4j.PatternLayout

与第4行相同。

  1. log4j.appender.R.layout.ConversionPattern=%d-[TS] %p %t %c - %m%n

      与第5行相同。
    
  1. log4j.logger.com. neusoft =DEBUG

指定com.neusoft包下的所有类的等级为DEBUG。

可以把com.neusoft改为自己项目所用的包名。

  1. log4j.logger.com.opensymphony.oscache=ERROR

    `og4j.logger.net.sf.navigator=ERROR`
    
    这两句是把这两个包下出现的错误的等级设为ERROR,如果项目中没有配置EHCache,则不需要这两句。
    
    
    
  2. log4j.logger.org.apache.commons=ERROR

    `log4j.logger.org.apache.struts=WARN`
    
    这两句是struts的包。
    
    
    
  3. log4j.logger.org.displaytag=ERROR

    这句是displaytag的包。(QC问题列表页面所用)
    
    
    
  4. log4j.logger.org.springframework=DEBUG

    此句为Spring的包。
    
    
    
  5. log4j.logger.org.hibernate.ps.PreparedStatementCache=WARN

    ` log4j.logger.org.hibernate=DEBUG`
    
    此两句是hibernate的包。
    

以上这些包的设置可根据项目的实际情况而自行定制。

步骤三:配置log4j为日志实现

mybatis-config.xm文件中配置log4j为日志实现

<!--配置log4j为日志实现-->
<settings>
    <setting name="logImpl" value="LOG4J"/>
</settings>

步骤四:log4j的使用

  • 在要使用Log4j的类中,导入import org.apache.log4j.Logger;
  • 创建日志对象,参数为当前类的class

在要输出日志的类中加入相关语句来定义属性:

static Logger logger = Logger.getLogger(UserMapperTest.class);
public class UserMapperTest {
    static Logger logger = Logger.getLogger(UserMapperTest.class);

    @Test
    public void testLog4j() {
        logger.info("info:进入了testLog4j方法");
        logger.debug("debug:进入了进入了testLog4j方法");
        logger.error("debug:进入了进入了testLog4j方法");
    }
}
2021-08-25 01:55:30 [DEBUG]-LogFactory - Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
2021-08-25 01:55:30 [DEBUG]-PooledDataSource - PooledDataSource forcefully closed/removed all connections.
2021-08-25 01:55:30 [DEBUG]-PooledDataSource - PooledDataSource forcefully closed/removed all connections.
2021-08-25 01:55:30 [DEBUG]-PooledDataSource - PooledDataSource forcefully closed/removed all connections.
2021-08-25 01:55:30 [DEBUG]-PooledDataSource - PooledDataSource forcefully closed/removed all connections.
2021-08-25 01:55:31 [INFO]-UserMapperTest - info:进入了testLog4j方法
2021-08-25 01:55:31 [DEBUG]-UserMapperTest - debug:进入了进入了testLog4j方法
2021-08-25 01:55:31 [ERROR]-UserMapperTest - debug:进入了进入了testLog4j方法

​ log4j定义了8个级别的log(除去OFF和ALL,可以说分为6个级别),优先级从高到低依次为:

  • OFF
  • FATAL
  • ERROR
  • WARN
  • INFO
  • DEBUG
  • TRACE
  • ALL
  1. ALL 最低等级的,用于打开所有日志记录。
  2. TRACE designates finer-grained informational events than the DEBUG.Since:1.2.12,很低的日志级别,一般不会使用。
  3. DEBUG 指出细粒度信息事件对调试应用程序是非常有帮助的,主要用于开发过程中打印一些运行信息。
  4. INFO 消息在粗粒度级别上突出强调应用程序的运行过程。打印一些你感兴趣的或者重要的信息,这个可以用于生产环境中输出程序运行的一些重要信息,但是不能滥用,避免打印过多的日志。
  5. WARN 表明会出现潜在错误的情形,有些信息不是错误信息,但是也要给程序员的一些提示。
  6. ERROR 指出虽然发生错误事件,但仍然不影响系统的继续运行。打印错误和异常信息,如果不想输出太多的日志,可以使用这个级别。
  7. FATAL 指出每个严重的错误事件将会导致应用程序的退出。这个级别比较高了。重大错误,这种级别你可以直接停止程序了。
  8. OFF 最高等级的,用于关闭所有日志记录。

如果将log level设置在某一个级别上,那么比此级别优先级高的log都能打印出来。例如,如果设置优先级为WARN,那么OFF、FATAL、ERROR、WARN 4个级别的log能正常输出,而INFO、DEBUG、TRACE、 ALL级别的log则会被忽略。Log4j建议只使用四个级别,优先级从高到低分别是ERROR、WARN、INFO、DEBUG。

九、动态SQL:x:

所谓的动态SQL,本质还是SQL语句,只是我们可以在SQL层面,去执行一些逻辑代码

8.1 if

动态 SQL 通常要做的事情是有条件地包含 where 子句的一部分。比如:

【示例】ArticleMapper.java

public interface ArticleMapper {

    List<Article> selectArticleByTitle(Article article);
}

【示例】Mapper.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.singerw.mapper.ArticleMapper">

    <select id="selectArticleByTitle" resultType="article" parameterType="article">
        select * from artilce where status = 1
        <if test="title !=null">
            and title like #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
    </select>

</mapper>

8.2 choose(when, otherwise)

​ 有些时候,我们不想用到所有的条件语句,而只想从中择其一二。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

【示例】Mapper.xml

<select id="queryBlogChoose" parameterType="map" resultType="com.rui.pojo.Blog">
    select * from mybatis.bolg
    <where>
        <choose>
            <when test="title != null">
                title=#{title}
            </when>
            <when test="author!=null">
                and author = #{author}
            </when>
            <otherwise>
                and views = #{views}
            </otherwise>
        </choose>
    </where>
</select>

【示例】Mapper.xml

8.3 trim(where, set)

【示例】Mapper.xml

select * from mybatis.bolg
<where>
    <if test="title != null">
        title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
</where>

【示例】Mapper.xml

<update id="updateBlog" parameterType="map">
    update mybatis.bolg
    <set>
        <if test="title != null">
            title = #{title},
        </if>
        <if test="author != null">
            author = #{author},
        </if>
    </set>
    where id = #{id}
</update>

8.4 foreach

可以做查询和删除还要批量删除。

【示例】ArticleMapper.xml

select * from user where 1=1 and 
<foreach item="id" index="index" collection="ids"
         open="(" separator="or" close=")">
    #{id}
</foreach>

(id=1 or id=2 or id=3)

【示例】ArticleMapper.xml

<!--
select * from mybatis.bolg where 1=1 and (id=1 or id=2 or id=3)

我们现在传递一个万能的map,这个map中可以存在一个map
-->
<select id="queryBlogForeach" parameterType="map" resultType="com.rui.pojo.Blog">
    select * from mybatis.bolg
    <where>
        <foreach collection="ids" item="id" open="(" close=")" separator="or">
            id = #{id}
        </foreach>
    </where>
</select>

8.5 SQL片段

有的时候,我们可能会将一些公共的部分抽取出来,方便复用!

  1. 使用SQL标签抽取公共的部分

    <sql id="if-title-author">
    <if test="title != null">
     title = #{title}
    </if>
    <if test="author != null">
     and author = #{author}
    </if>
    </sql>
  2. 在需要使用的地方使用Include标签引用即可

    <select id="queryBlogIF" parameterType="map" resultType="com.rui.pojo.Blog">
    select * from mybatis.bolg
    <where>
     <include refid="if-title-author"></include>
    </where>
    </select>

注意事项:

  • 最好基于单表来定义SQL片段!
  • 不要存在where或者set标签,片段里尽量只有if就好了

8.6 完整动态sql案例

十、实现分页功能:face_with_head_bandage:

9.1 Limit实现分页

通过SQL的Limit实现基本的分页功能。SQL层面的分页实现。

【示例】接口

public interface ArticleMapper {

    List<Article> getArticlesByLimit(Map<String,Integer> map);
}

【示例】Mapper.xml

<mapper namespace="com.singerw.mapper.ArticleMapper">
    <select id="getArticlesByLimit" resultType="Article" parameterType="map">
        select * from article limit #{page},#{pageSize};
    </select>
</mapper>

【示例】单元测试

public class ArticleMapperTest {

    @Test
    public void getArticleByLimit() {
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        ArticleMapper mapper = sqlSession.getMapper(ArticleMapper.class);

        HashMap<String, Integer> map = new HashMap<>();
        map.put("page",0);
        map.put("pageSize",10);
        List<Article> articles = mapper.getArticlesByLimit(map);
        articles.forEach(System.out::println);
    }
}

9.2 RowBounds实现分页

通过Java代码层面实现分页功能。

【示例】接口

public interface ArticleMapper {

    List<Article> getArticlesByRowBounds();
}

【示例】Mapper.xml

<mapper namespace="com.singerw.mapper.ArticleMapper">
    <select id="getArticlesByRowBounds" resultType="Article">
        select * from article;
    </select>
</mapper>

【示例】单元测试

public class ArticleMapperTest {

    @Test
    public void getArticleByRowBounds() {
        RowBounds rowBounds = new RowBounds(0,10);
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        List<Article> articles = sqlSession.selectList("com.singerw.mapper.ArticleMapper.getArticlesByRowBounds",null,rowBounds);
        articles.forEach(System.out::println);
        sqlSession.close();
    }
}

9.3 MyBatis 分页插件 PageHelper实现分页

​ 如果你也在用 MyBatis,建议尝试该分页插件,这一定是最方便使用的分页插件。分页插件支持任何复杂的单表、多表分页。

文档:https://pagehelper.github.io/docs/

十一、MyBatis缓存:call_me_hand:

11.1 缓存

  1. 什么事缓存[Cache]?

    • 存在内存中的临时数据。
    • 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,

    从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。

  2. 为什么使用缓存?

    • 减少和数据库的交互次数,减少系统开销,提高系统效率。
  3. 什么样的数据能使用缓存?

    • 经常查询并且不经常改变的数据。

11.2 MyBatis缓存

  • MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
  • MyBatis系统中默认定义了两级缓存:一级缓存二级缓存

    • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
    • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
    • 为了提扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存

11.3 一级缓存

一级缓存也叫本地缓存:SqlSession,SqlSession级别的缓存,也称为本地缓存

  • 与数据库同义词会话期间查询到的数据会放在本地缓存中。
  • 以后如果需要获取相同的数据,直接从缓存中拿,没有必要再去查询数据;

【基本使用步骤示例】

  1. 开启日志!
  2. 测试在一个Session中查询两次相同的记录
  3. 查看日志输出

缓存失效的情况:

  1. 查询不同的东西
  2. 增删改操作,可能会改变原来的数据,所以必定会刷新缓存!
  3. 查询不同的Mapper.xml
  4. 手动清理缓存!
sqlsession.clearCache(); //手动清理缓存

小节:一级缓存默认是开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个区间段!

一级缓存就是一个Map。

@Test
public void getArticles() {
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    ArticleMapper mapper = sqlSession.getMapper(ArticleMapper.class);
    List<Article> articles1 = mapper.getArticles("%VUE%");
    for (Article article : articles1) {
        System.out.println(article);
    }
    List<Article> articles2 = mapper.getArticles("%VUE%");
    for (Article article : articles2) {
        System.out.println(article);
    }
    System.out.println(articles1==articles2);
}

2021-08-26 15:14:58 DEBUG - ==> Preparing: SELECT article.id, article.title, article.content, article.summary, article.created, article.status, category.id AS cid, category.catename, category.created AS ccreated, user.id AS uid, user.username, user.nickname FROM article INNER JOIN category ON article.cid = category.id INNER JOIN user ON article.uid = user.id WHERE article.title LIKE ?

2021-08-26 15:14:58 DEBUG - ==> Parameters: %VUE%(String)

2021-08-26 15:14:59 DEBUG - <== Total: 1

Article(id=2, title=VUE, content=VUE是极简的前端框架, summary=vue, category=null, users=Users(id=1, username=tom, nickname=唐木松, password=null, status=0, email=null, userface=null, created=null, lastlogin=null, articleList=null, categoriesList=null), created=2020-12-25 01:20:20, status=1)

Article(id=2, title=VUE, content=VUE是极简的前端框架, summary=vue, category=null, users=Users(id=1, username=tom, nickname=唐木松, password=null, status=0, email=null, userface=null, created=null, lastlogin=null, articleList=null, categoriesList=null), created=2020-12-25 01:20:20, status=1)
true

@Test
public void getArticles() {
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    ArticleMapper mapper1 = sqlSession.getMapper(ArticleMapper.class);
    List<Article> articles1 = mapper1.getArticles("%VUE%");
    for (Article article : articles1) {
        System.out.println(article);
    }

    ArticleMapper mapper2 = sqlSession.getMapper(ArticleMapper.class);
    List<Article> articles2 = mapper2.getArticles("%VUE%");
    for (Article article : articles2) {
        System.out.println(article);
    }

    System.out.println(articles1==articles2);


    System.out.println("mapper1:"+mapper1);
    System.out.println("mapper2:"+mapper2);
    System.out.println(mapper1==mapper2);
}

2021-08-26 15:17:37 DEBUG - ==> Preparing: SELECT article.id, article.title, article.content, article.summary, article.created, article.status, category.id AS cid, category.catename, category.created AS ccreated, user.id AS uid, user.username, user.nickname FROM article INNER JOIN category ON article.cid = category.id INNER JOIN user ON article.uid = user.id WHERE article.title LIKE ?

2021-08-26 15:17:37 DEBUG - ==> Parameters: %VUE%(String)

2021-08-26 15:17:37 DEBUG - <== Total: 1

Article(id=2, title=VUE, content=VUE是极简的前端框架, summary=vue, category=null, users=Users(id=1, username=tom, nickname=唐木松, password=null, status=0, email=null, userface=null, created=null, lastlogin=null, articleList=null, categoriesList=null), created=2020-12-25 01:20:20, status=1)

Article(id=2, title=VUE, content=VUE是极简的前端框架, summary=vue, category=null, users=Users(id=1, username=tom, nickname=唐木松, password=null, status=0, email=null, userface=null, created=null, lastlogin=null, articleList=null, categoriesList=null), created=2020-12-25 01:20:20, status=1)

true

mapper1:org.apache.ibatis.binding.MapperProxy@3012646b
mapper2:org.apache.ibatis.binding.MapperProxy@4a883b15
false

11.4 二级缓存

  • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
  • 工作机制

    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
    • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据会被保存到二级缓存中;
    • 新的会话查询信息,就可以从二级缓存中获取内容;
    • 不同的mapper查出的数据会放在自己对应的缓存(map)中;

【基本使用步骤示例】

  1. 开启全局缓存

    <!--显式的开启全局缓存-->
    <setting name="cacheEnabled" value="true"/>
  2. 在要使用二级缓存的Mapper中开启

    <!--在当前Mapper.xml中使用二级缓存-->
    <cache/>
    
    
    
    也可以自定义参数
    <cache eviction="FIFO"
           flushInterval="60000"
           size="512"
           readOnly="true"/>
  3. 测试

问题:我们需要将实体类序列化!否则就会报错

java.io.NotSerializableException: com.rui.pojo.User

小结:

  • 只要开启了二级缓存,在同一个Mapper.java下就有效
  • 所有的数据都会先放在一级缓存中;
  • 只有当会话提交,或者关闭的时候,才会提交到二级缓存中!

11.6 自定义缓存-encache

EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。

要在程序中使用ehcache,先要导包!

<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.1.0</version>
</dependency>

然后在mapper中指定使用ehcache缓存实现

<!--在当前Mapper.xml中使用二级缓存-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

导入配置文件 ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">
    <!--
       diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
       user.home – 用户主目录
       user.dir  – 用户当前工作目录
       java.io.tmpdir – 默认临时文件路径
     -->
    <diskStore path="java.io.tmpdir/Tmp_EhCache"/>
    <!--
       defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
     -->
    <!--
      name:缓存名称。
      maxElementsInMemory:缓存最大数目
      maxElementsOnDisk:硬盘最大缓存个数。
      eternal:对象是否永久有效,一但设置了,timeout将不起作用。
      overflowToDisk:是否保存到磁盘,当系统当机时
      timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
      timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
      diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
      diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
      diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
      memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
      clearOnFlush:内存数量最大时是否清除。
      memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
      FIFO,first in first out,这个是大家最熟的,先进先出。
      LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
      LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
   -->
    <defaultCache
                  eternal="false"
                  maxElementsInMemory="10000"
                  overflowToDisk="false"
                  diskPersistent="false"
                  timeToIdleSeconds="1800"
                  timeToLiveSeconds="259200"
                  memoryStoreEvictionPolicy="LRU"/>

    <cache
           name="cloud_user"
           eternal="false"
           maxElementsInMemory="5000"
           overflowToDisk="false"
           diskPersistent="false"
           timeToIdleSeconds="1800"
           timeToLiveSeconds="1800"
           memoryStoreEvictionPolicy="LRU"/>
</ehcache>

十四、mybatis中使用的配置文件示例

1、mybatis-config.xml完整配置文件示例

【示例】:mybatis-config.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>
    <!--加载properties文件到MyBatis配置文件中-->
    <properties resource="db.properties"></properties>

    <!--配置log4j为日志实现-->
    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>

    <!-- 别名 -->
    <typeAliases>
        <typeAlias type="com.singerw.pojo.Users" alias="Users" />
        <typeAlias type="com.singerw.pojo.Category" alias="Category" />
        <typeAlias type="com.singerw.pojo.Article" alias="Article" />
    </typeAliases>


    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <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>


    <!--每一个mapper.xml都需要在MyBatis核心配置文件中注册-->
    <mappers>
        <mapper resource="com/singerw/mapper/ArticleMapper.xml"/>
        <mapper resource="com/singerw/mapper/UsersMapper.xml"/>
        <mapper resource="com/singerw/mapper/CategoryMapper.xml"/>
    </mappers>

</configuration>

【示例】:db.properties

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/goku?serverTimezone=Asia/Shanghai
username=root
password=795200

2、映射器

<!--映射器-->
<!--每一个mapper.xml都需要在MyBatis核心配置文件中注册-->
<mappers>
    <mapper resource="com/singerw/mapper/UserMapper.xml"/>
    <mapper resource="com/singerw/mapper/ArticleMapper.xml"/>
</mappers>
<mappers>
    <mapper class="com.singerw.mapper.ArticleMapper"/>
</mappers>

3、类型别名

<!-- 别名 -->
<typeAliases>
    <typeAlias type="com.singerw.pojo.User" alias="User" />
    <typeAlias type="com.singerw.pojo.Article" alias="Article" />
</typeAliases>

4、数据库链接信息属性

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/goku?serverTimezone=Asia/Shanghai
username=root
password=795200
<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <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>

5、ResultMat

【示例】数据库中的User表字段

  • userid
  • username
  • userphone
  • userpassword
  • jurisdiction
  • createtime
  • logintime
  • userstatus

数据库中的字段和实体类中的字段不一致。

使用结果集映射

【示例】UserMapper.xml

<!--resultMap结果集映射-->
<resultMap id="userMap" type="User">
    <!-- 实体类和表列做关联-->
    <!-- id 主键 属性 -->
    <id property="id" column="userid"></id>
    <!--其他部分-->
    <!--column数据库中的字段,property实体类中的属性-->
    <result property="name" column="username"></result>
    <result property="phone" column="userphone"></result>
    <result property="password" column="userpassword"></result>
    <result property="jurisdiction" column="jurisdiction"></result>
    <result property="juri" column="createtime"></result>
    <result property="created" column="createtime"></result>
    <result property="logined" column="logintime"></result>
    <result property="status" column="userstatus"></result>
</resultMap>

collection关联映射案例通过用户ID查询用户和用户发表的文章

<!-- 映射关系的管理和定义 ,type指的是实体类型,id当前的resultMap的名字 -->
<resultMap type="Users" id="userMap2">
    <!-- 主键使用id -->
    <id property="id" column="userid"/>
    <!-- 一个result表示一个列和实体类的属性的对应关系 -->
    <result property="username" column="username"/>
    <result property="nickname" column="nickname"/>
    <result property="password" column="password"/>
    <result property="status" column="userstatus"/>
    <result property="email" column="email"/>
    <result property="userface" column="userface"/>
    <result property="created" column="user_created"/>
    <result property="lastlogin" column="lastlogin"/>

    <collection property="articleList" ofType="Article">
        <id property="id" column="articleid"/>
        <result property="title" column="title"/>
        <result property="content" column="content"/>
        <result property="summary" column="summary"/>
        <result property="created" column="category_created"/>
        <result property="status" column="articlestatus"/>
    </collection>
</resultMap>

association关联映射,通过关键字或者文章ID查询一篇或多篇文章和每篇文章的作者信息

<!-- 映射关系的管理和定义 ,type指的是实体类型(取了别名),id当前的resultMap的名字 -->
<resultMap type="Article" id="articleMap">
    <!-- 主键使用id -->
    <id property="id" column="id"/>
    <!-- 一个result表示一个列和实体类的属性的对应关系 -->
    <result property="title" column="title"/>
    <result property="content" column="content"/>
    <result property="summary" column="summary"/>
    <result property="created" column="created"/>
    <result property="status" column="status"/>
    <!-- 此时我们的Article类中,增加了一个Users类型的属性 -->
    <association property="users" column="uid" javaType="com.singerw.pojo.Users">
        <!-- author类对应的表中的列 和 类属性关联 -->
        <id property="id" column="uid"/>
        <result property="username" column="username"/>
        <result property="nickname" column="nickname"/>
    </association>
</resultMap>

6、开启全局缓存

<!--显式的开启全局缓存-->
<setting name="cacheEnabled" value="true"/>

在要使用二级缓存的Mapper.xml中开启

<!--在当前Mapper.xml中使用二级缓存-->
<cache/>



也可以自定义参数
<cache eviction="FIFO"
       flushInterval="60000"
       size="512"
       readOnly="true"/>

十五、MyBatisUitls.java工具类:zap:

package com.singerw.utils;

import java.io.IOException;
import java.io.InputStream;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class SqlSessionUtil {

    private static SqlSessionFactory sqlSessionFactory = null;

    // 使用ThreadLocal 来管理我们的 SqlSession
    private static ThreadLocal<SqlSession> threadLocal = new ThreadLocal<SqlSession>();

    static {
        // 指定mybaits的全局配置文件的路径
        String resource = "mybatis-config.xml";
        // 输入流读取配置文件
        InputStream inputStream = null;
        try {
            inputStream = Resources.getResourceAsStream(resource);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        // 通过FactoryBuilder 得到 SqlSessionFactory
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        // 释放inputStream
        try {
            inputStream.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * 
     * @return
     */
    public static SqlSession getSqlSession() {

        // 要先去threadLocal 看看是否有和当前线程绑定的那个sqlsession如果有 直接使用 没有再创建 op~ session
        SqlSession session = threadLocal.get();
        if (session == null) {
            // 通过sqlSessionFactory得到SqlSession
            session = sqlSessionFactory.openSession();
            // 得到了这个session,就将其放在threadLocal中
            threadLocal.set(session);
        }
        return session;
    }

    /**
     * 
     */
    public static void closeSqlSession() {
        // 从当前线程中获取 SqlSession对象
        SqlSession session = threadLocal.get();
        // 如果对象 不为空
        if (session != null) {
            // 从threadLocal 中移除
            threadLocal.remove();
            session.close();
        }
    }
}
最后修改:2021 年 08 月 30 日 01 : 54 AM
如果觉得我的文章对你有用,请随意赞赏