快速上手Mybatis

一、引言

1.1 什么是框架

软件的半成品,解决了软件过程当中的普适性问题,从而简化了开发步骤,提供了开发的效率。

1.2 什么是ORM框架

  • ORM(Object Relation Mapping)对象关系映射,将程序中的一个对象与表中的一行数据一一对应。
  • ORM框架提供了持久化类与表的映射关系,在运行时参照映射文件的信息,把对象持久化到数据库中。

1.3 使用JDBC完成ORM操作的缺点

  • 存在大量的冗余代码。
  • 手工创建Connection、Statement等。
  • 手工将结果集封装成实体对象。
  • 查询效率低,没有对数据访问进行过优化(Not Cache)。

二、简介

2.1 概念

  • Mybatis本事Apache软件基金会的一个开源项目iBatis,2010年这个项目由apache software foundation迁移到了Google Code,并且改名为Mybatis。2013年11月迁移到GitHub。
  • Mybatis是一个优秀的基于Java的持久层框架,支持自定义SQL,存储过程和高级映射。
  • Mybatis对原有JDBC操作进行了封装,几乎消除了所有JDBC代码,使开发者只需关注SQL本身。
  • Mybatis可以使用简单的XML或Annotation来配置执行SQL,并自动完成ORM操作,将执行结果返回。

2.2 访问与下载

官方网址:http://www.mybatis.org/mybatis-3/

下载地址:https://github.com/mybatis/mybatis-3/releases/tag/mybatis-3.5.1

三、环境搭建

3.1 构建Maven项目

在开发工具中新建一个Maven项目,并在pom文件中导入相关依赖:

<!-- 导入依赖 -->
<dependencies>
    <!-- Mybatis核心依赖 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.4.6</version>
    </dependency>
    <!-- Mysql驱动依赖 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.21</version>
    </dependency>
</dependencies>

3.2 创建Mybatis配置文件

新建一个名为mybatis-config的xml文件,写入如下配置:

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mytais.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 核心配置信息 -->
    <environments default="lazydog_config">
        <!-- 数据库相关配置 -->
        <environment id="lazydog_config">
            <!-- 事务控制类型 -->
            <transactionManager type="jdbc"></transactionManager>
            <!-- 数据连接参数 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <!-- &转义&amp; -->
                <property name="url" value="jdbc:mysql://localhost:3306/userdb?useUnicode=true&amp;characterEncoding=utf-8&amp;serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="170912036"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

environment标签中id属性指一套数据库配置的标识,任意取;environments标签中的id属性填需要生效的数据库配置的标识,这里只有一个,所以同为lazydog_config。

该配置使用了我已经有的userdb数据库,当然你可以自己建立一个数据库和一些表。

四、Mybatis开发步骤

4.1 定义实体类

首先需要根据现有的数据库去建立一个对应的实体类。

public class User {
    private Integer id;
    private String username;
    private String password;
    private Boolean gender;
    private Date regist_time;
    //构造函数、get和set方法等已省略
}

4.2 定义DAO接口

mybatis会利用反射在代码运行过程中自动生成DAO接口的实现类,所以只需要写一个接口即可。

public interface UserDAO {
    public User queryUserById(Integer id);
}

4.3 编写映射文件

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mytais.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="lazydog.dao.UserDAO">
    <!-- 描述方法 -->
    <select id="queryUserById" resultType="lazydog.entity.User">
        select id,username,password,gender,regist_time
        from t_user
        where id=#{arg0}
    </select>
</mapper>

命名空间写所要生成的实现类对应的DAO层接口,select标签对应的是查询方法,id为在接口中定义的查询方法名,#{}中写方法的参数,arg0表示第一个参数。

该sql语句运行后得到的是一个结果集,但是最终会被封装成一个User类,属性名是如何对应的呢?

Mybatis在执行sql语句后会根据同名策略,把结果集中的数据对应到实体类中的一个个属性上。

4.4 注册Mapper

mybatis在运行时只加载mybatis配置文件,此处是mybatis-config.xml,所以仅仅编写映射文件是和mybatis没有联系的,需要在mybatis-config.xml文件中进行注册。

<configuration>
    <!-- 核心配置信息省略 -->
    <!-- 注册mapper文件 -->
    <mappers>
        <mapper resource="UserDAOMapper.xml"/>
    </mappers>
</configuration>

4.5 运行测试

public class TestMybatis {
    public static void main(String[] args) throws IOException {
        //1.加载配置文件
        InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
        //2.构建SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        //3.创建SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //4.获得UserDAO实现类的对象
        UserDAO mapper = sqlSession.getMapper(UserDAO.class);
        //5.测试查询方法
        User user = mapper.queryUserById(1);
        User user1 = mapper.queryUserById(2);
        System.out.println(user);
        System.out.println(user1);
    }
}

查询成功,结果如下:

User{id=1, username='lazydog', password='990130', gender=true, regist_time=Sun Mar 07 06:51:55 CST 2021}
User{id=2, username='lazycat', password='990206', gender=false, regist_time=Sun Mar 07 06:52:16 CST 2021}

之前说过既然是同名策略,倘若数据表中的列名和实体类中的属性名不一样时还能查询成功吗?

如果不一样,那么查到的值就会是null,修正的方法也很简单,编写sql语句时只需要as一个别名,使两者相同便可以了。

4.6 细节补充

4.6.1 映射文件的存放路径

由于DAO文件和对应的mapper.xml配置关系密切,所以一般会将映射文件放置于DAO层同目录下。那么在注册mapper映射文件时,就要相应的修改路径:

<mappers>
    <mapper resource="lazydog/dao/UserDAOMapper.xml"/>
</mappers>

但是在Maven中,会把main/java下的文件都视为java文件,从而忽略掉xml文件,直接运行会报找不到映射文件的错误,这时候还要修改一下maven的默认设置,在pom文件中添加如下标签:

<build>
    <!-- 更改Maven编译规则 -->
    <resources>
        <resource>
            <!-- 资源目录 -->
            <directory>src/main/java</directory>
            <includes>
                <!--默认(若有新添加的自定义则失效)-->
                <include>*.xml</include>
                <!-- 新添加(*/代表一级目录,**/代表多级目录) -->
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>

4.6.2 Properties配置文件

在mybatis的配置文件中,我们直接书写了数据库的连接参数(3.2节),但是在实际开发中,这些连接参数可能需要频繁变动,比如从一个开发环境变到测试环境中等,那么可以把这些需要频繁变动的配置单独写进一个文件,便于随时查看修改。

新建一个jdbc.properties文件,当然文件名不是固定的,配置如下:

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/userdb?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
jdbc.username=root
jdbc.password=170912036

左边的值也不是固定的,可任取。

把这些参数写进新的单独文件后,mybatis配置文件自然要和properties文件联系起来,在其中添加如下标签:

<configuration>
    <!-- 导入外部的参数 -->
    <properties resource="jdbc.properties"/>
</configuration>

mybatis原来的连接参数改成如下形式:

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

其中${}括号内填properties文件中相应的名字,只要properties改动了,mybatis配置文件也会发生改变,也就是动态关联。

4.6.3 创建类型别名

在4.3节中的select标签中,我们书写了一个完整的类名lazydog.entity.User,但实际上我们肯定会有不止这一个描述方法,所有的方法标签都要写一个完整的类名是比较繁琐的。所以我们可以给这个完整类名定义一个简短的别名,来简化书写并达到相同的效果。

在mybatis核心配置中添加如下标签:

<!-- 实体类别名 -->
<typeAliases>
    <typeAlias type="lazydog.entity.User" alias="User"/>
</typeAliases>

这样为User实体类定义了一个别名User,在方法标签中使用只需要写别名即可:

<select id="queryUserById" resultType="User">
    select id,username,password,gender,regist_time
    from t_user
    where id=#{arg0}
</select>

但是这种方法也有不足之处,就是在数据表中如果对应着多个实体类(实际也常如此),那么我们需要手动为每一个实体类定义一个别名。所以通常建议使用第二种方法,在mybatis核心配置中添加如下标签:

<!-- 实体类别名 -->
<typeAliases>
    <!-- 定义实体类所在的package,每个实体类都会自动注册一个别名 -->
    <package name="lazydog.entity"/>
</typeAliases>

用这种方法注册的别名与报下的类名相同,比如User实体类的别名就是User。

这两种方法二选一即可,不要都写。

4.6.4 创建日志配置文件

在4.5节中运行测试时,控制台只打印了输出结果,是看不到mybatis在执行过程中做过哪些操作的,如果想看其中的一些记录细节,可以配置一个日志文件。

首先在pom中导入一个日志依赖,比较出名的log4j:

<!-- 日志依赖 -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

然后在resource目录中编写文件log4j.properties(文件名为固定格式):

# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

再次运行测试代码得到如下结果:

DEBUG [main] - Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
DEBUG [main] - Class not found: org.jboss.vfs.VFS
DEBUG [main] - JBoss 6 VFS API is not available in this environment.
DEBUG [main] - Class not found: org.jboss.vfs.VirtualFile
DEBUG [main] - VFS implementation org.apache.ibatis.io.JBoss6VFS is not valid in this environment.
DEBUG [main] - Using VFS adapter org.apache.ibatis.io.DefaultVFS
DEBUG [main] - Find JAR URL: file:/E:/apache-tomcat-9.0.20/webapps/hello-mybatis/target/classes/lazydog/entity
DEBUG [main] - Not a JAR: file:/E:/apache-tomcat-9.0.20/webapps/hello-mybatis/target/classes/lazydog/entity
DEBUG [main] - Reader entry: User.class
DEBUG [main] - Listing file:/E:/apache-tomcat-9.0.20/webapps/hello-mybatis/target/classes/lazydog/entity
DEBUG [main] - Find JAR URL: file:/E:/apache-tomcat-9.0.20/webapps/hello-mybatis/target/classes/lazydog/entity/User.class
DEBUG [main] - Not a JAR: file:/E:/apache-tomcat-9.0.20/webapps/hello-mybatis/target/classes/lazydog/entity/User.class
DEBUG [main] - Reader entry: ����   4 P
DEBUG [main] - Checking to see if class lazydog.entity.User matches criteria [is assignable to Object]
DEBUG [main] - PooledDataSource forcefully closed/removed all connections.
DEBUG [main] - PooledDataSource forcefully closed/removed all connections.
DEBUG [main] - PooledDataSource forcefully closed/removed all connections.
DEBUG [main] - PooledDataSource forcefully closed/removed all connections.
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 798981583.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2f9f7dcf]
DEBUG [main] - ==>  Preparing: select id,username,password,gender,regist_time from t_user where id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
DEBUG [main] - ==>  Preparing: select id,username,password,gender,regist_time from t_user where id=? 
DEBUG [main] - ==> Parameters: 2(Integer)
DEBUG [main] - <==      Total: 1
User{id=1, username='lazydog', password='990130', gender=true, regist_time=Sun Mar 07 06:51:55 CST 2021}
User{id=2, username='lazycat', password='990206', gender=false, regist_time=Sun Mar 07 06:52:16 CST 2021}

输出结果在最后两行,其他部分则是日志记录,有一些类的识别,还能看到连接池的管理,控制事务,但重点关注的是后面的一些信息:

DEBUG [main] - ==>  Preparing: select id,username,password,gender,regist_time from t_user where id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
DEBUG [main] - ==>  Preparing: select id,username,password,gender,regist_time from t_user where id=? 
DEBUG [main] - ==> Parameters: 2(Integer)
DEBUG [main] - <==      Total: 1

从这里我们可以看到mybatis所执行的sql语句,以及它所获取的参数值是什么,这些记录可以更好地让我们分析程序的运行过程。

五、Mybatis的CRUD操作

5.1 查询

在查询中,我们需要讨论各种参数的绑定过程。在上文中已经使用过arg0来绑定方法参数列表中第一个参数,接下来讨论多个参数的绑定。

5.1.1 序号参数绑定

首先增加一个多参数的查询方法:

User queryUserByIdAndName(Integer id,String username);

然后在映射文件中描述改方法:

<select id="queryUserByIdAndName" resultType="User">
    select id,username,password,gender,regist_time
    from t_user
    where id=#{arg0} and username=#{arg1}
</select>

此处使用了arg0绑定第一个参数,使用arg1绑定第二个参数。

编写测试代码,运行程序:

User user2 = mapper.queryUserByIdAndName(1, "lazydog");
System.out.println(user2);

正常运行。

还有一种方法是使用param1来绑定第一个参数,param2绑定第二个,不再演示。

5.1.2 注解参数绑定

序号参数绑定的不足在于可读性差,不知道arg0或者param1代表什么。使用注解参数绑定可以较好地解决这种弊端。

在DAO接口的方法定义中添加如下注释:

User queryUserByIdAndName(@Param("id") Integer id,@Param("username") String username);

修改映射文件中的描述方法:

<select id="queryUserByIdAndName" resultType="User">
    select id,username,password,gender,regist_time
    from t_user
    where id=#{id} and username=#{username}
</select>

测试代码略,测试结果正常。

这种写法也是比较推荐的,但主要偏向于参数数量较少的情况。在参数数量较多的情况下,可以使用后两种绑定方式。

5.1.3 Map参数绑定

将方法参数改成Map类型,在传入参数时将各个参数封装成Map对象。

User queryUserByIdAndName(Map map);

Map中存储的是键值对,键就是所要查的属性,值就是属性值。

那么在测试代码中就可以这样添加键值对:

HashMap userHashMap = new HashMap<>();
userHashMap.put("id","1");
userHashMap.put("username","lazydog");
User user3 = mapper.queryUserByIdAndName(userHashMap);
System.out.println(user3);

在映射文件的参数中,填的就是map的键,所以编写同上一节。

运行结果正常。

5.1.4 对象参数绑定

在参数数量较多的情况下,会将所要查询的条件封装成一个对应的实体类,比如User。在映射文件中的参数#{}括号中填写的就是属性名,从字面上看和前两节都是一样的,但含义不一样。

User queryUserByIdAndName(User user);

测试代码如下:

User user4 = new User();
user4.setId(1);
user4.setUsername("lazydog");
user4 = mapper.queryUserByIdAndName(user4);
System.out.println(user4);

测试正常。

5.1.5 模糊查询

如果要通过模糊查询查询多个对象,那么方法的返回值就不再是实体类了,而应该是一个集合:

List<User> queryUserByName(String username);

但是在映射文件中,resultType属性依然是实体类User,而不是集合。因为集合中封装的依然是一个个的User,在写映射文件时需要写的是集合的泛型类型。

在编写sql语句时会遇到一个问题,就是拼接问题,如果按照如下编写sql语句:

<select id="queryUserByName" resultType="User">
    select id,username,password,gender,regist_time
    from t_user
    where username like %#{username}%
</select>

这样会报一个语法错误,并且在日志中可以查看到mybatis所执行的sql语句:

Preparing: select id,username,password,gender,regist_time from t_user where username like %?% 

发现%?%出现了问题,解决这个问题,可以使用concat进行拼接,这是mysql的语法。

<select id="queryUserByName" resultType="User">
    select id,username,password,gender,regist_time
    from t_user
    where username like concat("%",#{username},"%")
</select>

然后测试:

List<User> userList = mapper.queryUserByName("lazy");
for (User user5:userList) {
    System.out.println(user5);
}

运行结果如下:

User{id=1, username='lazydog', password='990130', gender=true, regist_time=Sun Mar 07 06:51:55 CST 2021}
User{id=2, username='lazycat', password='990206', gender=false, regist_time=Sun Mar 07 06:52:16 CST 2021}

运行正常。

5.2 删除

增删改操作相比查询会简单很多,前文已陈述过参数绑定,所以这边也没什么可以展开说的,演示即可。

首先定义方法:

void deleteUserById(@Param("id") Integer id);

编写映射文件:

<delete id="deleteUserById" parameterType="int">
    delete from t_user
    where id=#{id}
</delete>

与查询不同的是,增删改没有resultType,有的是参数类型parameterType。

测试代码:

mapper.deleteUserById(1);
//提交事务
sqlSession.commit();

这里需要手动提交事务,否则数据库是不会进行更改的。

5.3 修改

定义修改方法:

void updateUserById(User user);

编写映射文件:

<update id="updateUserById" parameterType="User">
    update t_user
    set username=#{username},password=#{password},gender=#{gender},regist_time=#{regist_time}
    where id=#{id}
</update>

测试代码:

User user = new User(2,"lazypig","981214",true,new Date());
mapper.updateUserById(user);
sqlSession.commit();

运行正常,修改成功。

5.4 增加

定义增加方法:

Integer insertUser(User user);

这里的返回值是Integer而不是void,它可以返回受影响的行数,但是我们不需要任何额外的操作,Mybatis会自动会为该方法返回一个整数值,同理,删除修改也只需要修改返回类型为Integer就可以实现这样的操作。

编写映射文件:

<insert id="insertUser" parameterType="User">
    insert into t_user value(#{id},#{username},#{password},#{gender},#{regist_time})
</insert>

测试代码:

User user = new User(null,"lazyrabbit","981222",true,new Date());
mapper.insertUser(user);
sqlSession.commit();

这里id赋为null是因为表设置了主键自增,所以不用赋值;但是你也可以手动设置一个主键值。

运行正常。

5.5 主键回填

在上一节中User对象的id属性是没有赋值的,但由于自增策略,数据库中的所有字段都有值,但User对象的id属性确是null,如果添加完数据后需要用户编号(或者订单编号),那我们是不知道这个数据的,则代码便无法继续。

如果User要得到新添加的数据的id属性值,那么可以进行主键回调。

修改映射文件:

<insert id="insertUser" parameterType="User">
    <!-- 主键回填,将新数据的id,存入java对象和主键对应的属性中 -->
    <selectKey order="AFTER" resultType="int" keyProperty="id">
        select last_insert_id()
    </selectKey>
    insert into t_user value(#{id},#{username},#{password},#{gender},#{regist_time})
</insert>

last_insert_id()是mysql函数,它获取最近一次插入的主键的值。

insert标签中有了两句sql语句,order属性决定了执行先后,因为先插入数据才有主键id值,所以获取主键函数在后,order值为AFTER。

resultType为返回的主键类型;keyProperty为回填的属性,此处为User类的id。

编写测试代码,查看回填是否成功:

User user = new User(null,"lazydog","990130",true,new Date());
mapper.insertUser(user);
sqlSession.commit();
System.out.println(user);

打印台输出如下,id不为null:

User{id=4, username='lazydog', password='990130', gender=true, regist_time=Sun Mar 07 21:17:17 CST 2021}

实际开发中并不是所有主键都为整型且自增,下面处理一个逐渐类型为字符串的表。

新建一个Student表,创建对应的实体类:

public class Student {
    private String id;
    private String name;
    private Boolean gender;
    //其他代码已省略
}

编写映射文件:

<mapper namespace="lazydog.dao.StudentDAO">
    <insert id="insertStudent" parameterType="Student">
        <selectKey order="BEFORE" keyProperty="id" resultType="string">
            select UUID()
        </selectKey>
        insert into t_student
        value(#{id},#{name},#{gender})
    </insert>
</mapper>

uuid()函数会生成一个诸如45892fd4-7f4f-11eb-bb7f-00ffe3661b49的32位全球唯一的字符串(不包括四个横杠),这也是大多数id是32位的原因。

在添加数据时,我们会给数据项的id值一个null值,所以需要先进行主键回填给id一个确定值,然后执行insert语句,所以order为BEFORE。

但是在建表时,id的类型为32位字符串,uuid实际包括横杠的长度是36位,所以运行会报id过长的错误,进行如下修改即可:

select replace(UUID(),'-','')

replace也是mysql函数,上面的代码将“-”替换成了空值。

编写测试代码:

public class TestMybatis {
    public static void main(String[] args) throws IOException {
        //1.加载配置文件
        InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
        //2.构建SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        //3.创建SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //4.获得UserDAO实现类的对象
        StudentDAO mapper = sqlSession.getMapper(StudentDAO.class);
        //5.测试查询方法
        Student student=new Student(null,"tang",false);
        mapper.insertStudent(student);
        System.out.println(student);
        sqlSession.commit();
    }
}

数据库添加成功,打印台正常输出id值。

六、Mybatis工具类

在前文编写测试代码时,有很多重复的代码,这些繁琐的代码增加了工作量,我们可以将这些重复的代码写进一个单独的类,并设置一些方法可以从外部直接调用想用的功能,接下来就编写工具类。

public class MybatisUtils {
    //创建SqlSession工厂
    private static SqlSessionFactory factory;
    //创建ThreadLocal绑定当前线程中的SqlSession对象
    private static final ThreadLocal<SqlSession> t1=new ThreadLocal<>();
    static {
        try {
            InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
            factory=new SqlSessionFactoryBuilder().build(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //获取SqlSession
    public static SqlSession openSession(){
        SqlSession sqlSession=t1.get();
        if (sqlSession == null){
            sqlSession=factory.openSession();
            t1.set(sqlSession);
        }
        return sqlSession;
    }
    //获取Mapper,DAO接口对应的实现类对象
    public static <T> T getMapper(Class<T> mapper){
        SqlSession sqlSession = openSession();
        return sqlSession.getMapper(mapper);
    }
    //释放资源
    public static void close(){
        SqlSession sqlSession=t1.get();
        sqlSession.close();
    }
    //提交事务
    public static void commit(){
        SqlSession sqlSession=openSession();
        sqlSession.commit();
        close();
    }
    //回滚事务
    public static void rollback(){
        SqlSession sqlSession=openSession();
        sqlSession.rollback();
        close();
    }
}

接下来编写测试类:

public class TestMybatis {
    public static void main(String[] args) throws IOException {
        StudentDAO mapper = MybatisUtils.getMapper(StudentDAO.class);
        Student student = new Student(null, "tang", false);
        mapper.insertStudent(student);
        MybatisUtils.commit();
    }
}

同上一节比较,代码精简了不少。

七、ORM映射

在4.3节说过Mybatis默认根据同名映射来对应实体类中的属性和表中的字段,当在字段中找不到同名的属性名时,就会映射失效,也叫做自动ORM失效

在4.5节中提到过当属性名和字段名不一样时,可以在编写SQL语句时使用AS来为字段设置一个别名;这里主要讲另外一种方法名字不一致时的解决方案。

以前文中User类为例,假设属性username变量名改成name,但表中字段还是username,那么在映射文件中可以进行如下配置:

<mapper namespace="lazydog.dao.UserDAO">
    <!-- 定义更复杂的映射规则 -->
    <resultMap id="user_resultMap" type="User">
        <id column="id" property="id"/>
        <result column="username" property="name"/>
        <result column="gender" property="gender"/>
    </resultMap>
</mapper>

resultMap标签中的id属性是一个标识,type是封装类。

id标签中的column是字段值,property是对应(映射)的实体类属性值,id代表主键。

在原先的select标签中的resultType属性也要改成resultMap,其值为resultMap标签的id值;这样在sql语句中就不需要使用别名。

这样写似乎更麻烦了,但在某些不适用于同名策略的情况下,这种写法将很常见。

八、Mybatis处理关联关系

在实际项目中不可能只有一张表,多张表之间也不一定是独立的。下面有两张存在着关联的表。

t_passengers(主表):

idnamesexbirthday
1001tangM1999-01-30
1002zhangF1998-10-1
1003heM1999-06-06

t_passports(从表):

idnationalityexpirepassenger_id
1000001China1999-01-301001
1000002America2025-01-011002
1000003Korea2030-01-011003

护照表有一个外键passenger_id,指向了旅客表的主键id,关联了旅客表,属于从表。它们之间是一对一的关系。

多表之间可分为三种关联关系:

  • OneToOne,一对一关系:passenger——passport
  • OneToMany,一对多关系:employee——department
  • ManyToMany,多对多关系:student——subject

8.1 一对一关系

在数据库中建立如上两张表,并创建相应实体类:

public class Passenger {
    private Integer id;
    private String name;
    private Character sex;
    private Date birthday;
    //注:存储旅客的护照信息
    private Passport passport;
    //其他代码省略
}
public class Passport {
    private Integer id;
    private String nationality;
    private Date expire;
    //注:存储所属的旅客信息
    private Passenger passenger;
    //其他代码省略
}

注:光有各自的属性两张表就会是独立的,要联系起来还要存储与之相关联的实体类,这种实体类属性被称为关系属性

定义一个方法,查询旅客信息,同时查询结果还要求显示其护照信息

public interface PassengerDAO {
    Passenger queryPassengerById(@Param("id") Integer id);
}

编写映射文件:

<?xml version="1.0" encoding="gbk" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mytais.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="lazydog.dao.PassengerDAO">
    <select id="queryPassengerById" resultType="Passenger">
        select t_passengers.id,t_passengers.name,t_passengers.sex,t_passengers.birthday,
        t_passports.id as port_id,t_passports.nationality,t_passports.expire
        from t_passengers join t_passports  on t_passengers.id = t_passports.id
        where passenger_id=#{id}
    </select>
</mapper>

为了区分两个id,为护照id取了一个别名port_id。

如果单是根据前文的知识,那么就是这样的映射文件。但是想想其中的映射关系,该sql语句查询出来的是一个一行七列的表,拥有七个字段,如果将其封装到Passenger类中,那么映射关系是怎样的?根据同名策略,Passenger只有五个属性,显然上述的映射文件写法是错误的。

在第七节中提到了另外一种映射方式,所以使用resultMap来定义映射规则:

<mapper namespace="lazydog.dao.PassengerDAO">
    <resultMap id="passenger_passport" type="Passenger">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="sex" property="sex"/>
        <result column="birthday" property="birthday"/>
        <association property="passport" javaType="Passport">
            <id column="port_id" property="id"/>
            <result column="nationality" property="nationality"/>
            <result column="expire" property="expire"/>
        </association>
    </resultMap>
    <select id="queryPassengerById" resultMap="passenger_passport">
        select t_passengers.id,t_passengers.name,t_passengers.sex,t_passengers.birthday,
        t_passports.id as port_id,t_passports.nationality,t_passports.expire
        from t_passengers join t_passports  on t_passengers.id = t_passports.id
        where passenger_id=#{id}
    </select>
</mapper>

其中association标签就是一个级联映射,它将旅客类的passport属性映射到护照类的id、nationality、expire属性上。

javaType指的是所关系的实体类。

编写测试代码:

PassengerDAO mapper = MybatisUtils.getMapper(PassengerDAO.class);
Passenger passenger = mapper.queryPassengerById(1);
System.out.println(passenger);

运行正常。(不要忘记注册mapper)


通过旅客查到了护照信息,那么同理也可以在查询护照的同时查询旅客,使得一对一的关系变成双向。

由于操作步骤雷同,只是映射文件旅客和护照相关代码互换即可,所以不再演示。

8.2 一对多关系

以员工和部门为例,一个部门对应多个员工,而多个员工属于一个部门。建立员工表和部门表,添加少量数据供查询使用,并创建实体类:

public class Department {
    private int id;
    private String name;
    private String location;
    //部门所包含的员工列表
    private List<Employee> employees;
    //其他代码省略
}
public class Employee {
    private int id;
    private String name;
    private Double salary;
    //员工所从属的部门信息
    private Department department;
    //其他代码省略
}

因为一个部门有可能不止一个员工,所以需要用List来接收员工信息。

定义查询方法:

public interface DepartmentDAO {
    //查询部门,及其部门员工信息
    Department queryDepartmentById(@Param("id") Integer id);
}

编写映射文件:

<?xml version="1.0" encoding="gbk" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mytais.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="lazydog.dao.DepartmentDAO">
    <resultMap id="dept_emp" type="Department">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="location" property="location"/>
        <collection property="employees" ofType="Employee">
            <id column="emp_id" property="id"/>
            <result column="name" property="name"/>
            <result column="salary" property="salary"/>
        </collection>
    </resultMap>
    <select id="queryDepartmentById" resultMap="dept_emp">
        select t_departments.id,t_departments.name,t_departments.location,
        t_employees.id as emp_id,t_employees.name,t_employees.salary
        from t_departments join t_employees
        on t_departments.id = t_employees.dept_id
        where t_departments.id=#{id}
    </select>
</mapper>

在处理一对多的映射时,由于employees是集合,需要用collection标签来映射,ofType的值为集合泛型类型。

编写测试代码:

DepartmentDAO mapper = MybatisUtils.getMapper(DepartmentDAO.class);
Department department = mapper.queryDepartmentById(1);
System.out.println(department);
System.out.println(department.getEmployees());

运行正常。


通过部门查到了员工信息,那么同理也可以在查询员工的同时查询从属部门。

由于操作步骤雷同,所以不再演示。

8.3 多对多关系

多对多关系中,需要建立第三张关系表,当中存储两张表中的外键,并设置联合主键。

建立科目表和学生表,添加些许数据以供查询使用:

create table t_student2(
    id int primary key auto_increment,
    name varchar(50),
    sex char(1)
)default charset =utf8;
insert into t_student2 values (1,'tang','m');
insert into t_student2 values (2,'li','f');
create table t_subject(
    id int primary key auto_increment,
    name varchar(50),
    grade int
)default charset =utf8
create table t_stu_sub(
    stu_id varchar(32),
    sub_id int,
    foreign key (stu_id) references userdb.t_student(id),
    foreign key (sub_id) references userdb.t_subject(id),
    primary key (stu_id,sub_id)
)default charset =utf8;
insert into t_subject values (1,'软件工程',3);
insert into t_subject values (2,'数据结构',2);
insert into t_stu_sub values (1,2),(1,1),(2,1),(2,2);

创建相应实体类:

public class Student2 {
    private Integer id;
    private String name;
    private Character sex;
    private List<Subject> subjects;
    //其他代码省略
}
public class Subject {
    private int id;
    private String name;
    private int grade;
    private List<Student2> student2s;
    //其他代码省略
}

定义查询方法,要求查询科目的同时查询所报课程的学生:

public interface SubjectDAO {
    Subject querySubjectById(@Param("id")Integer id);
}

编写映射文件:

<?xml version="1.0" encoding="gbk" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mytais.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="lazydog.dao.SubjectDAO">
    <resultMap id="stu_sub" type="Subject">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="grade" property="grade"/>
        <collection property="student2s" ofType="Student2">
            <id column="student2_id" property="id"/>
            <result column="student2_name" property="name"/>
            <result column="sex" property="sex"/>
        </collection>
    </resultMap>
    <select id="querySubjectById" resultMap="stu_sub">
        select t_subject.id,t_subject.name,t_subject.grade,
        t_student2.id as student2_id,t_student2.name as student2_name,t_student2.sex
        from t_subject join t_stu_sub on t_subject.id=t_stu_sub.sub_id
        join t_student2 on t_stu_sub.stu_id=t_student2.id
        where t_subject.id=#{id}
    </select>
</mapper>

sql语句中使用了多表连接,查询结果是以一个多行多列的表。

注册完mapper文件后编写测试代码:

SubjectDAO mapper = MybatisUtils.getMapper(SubjectDAO.class);
Subject subjects = mapper.querySubjectById(1);
System.out.println(subjects.getStudent2s());

结果正常。


通过科目查到了学生信息,那么同理也可以在查询学生的同时查询所学科目。

由于操作步骤雷同,所以不再演示。

九、动态SQL

9.1 SQL标签

前文映射文件中的sql语句是直接写的,其中有较为繁琐的地方。比如有多个根据不同条件的查询语句,但查询的结果都是一样的,那么就会在DAO文件中编写不同的查询方法,在写sql语句时就会发现有大部分语句都是重复的,或者说通用的;再者,假如某个表字段的名字改了,那么所有的查询sql语句都要改,这会很麻烦。

所以,可以考虑将重复/通用的语句提取出来,写的时候直接引用就行,这样就可以考虑sql标签。

编写两个查询方法:

public interface Student2DAO {
    Student2 queryStudent2ById(@Param("id") Integer id);
    Student2 queryStudent2ByName(@Param("name") String name);
}

编写映射文件:

<?xml version="1.0" encoding="gbk" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mytais.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="lazydog.dao.Student2DAO">
    <sql id="student2_filed">
        select t_student2.id,t_student2.name,t_student2.sex
        from t_student2
    </sql>
    <select id="queryStudent2ById" resultType="Student2">
        <include refid="student2_filed"/>
        where id=#{id}
    </select>
    <select id="queryStudent2ByName" resultType="Student2">
        <include refid="student2_filed"/>
        where name=#{name}
    </select>
</mapper>

sql标签中抽取重复/通用的语句,id为所抽取的sql语句的标识,自己取。

然后在include标签中引用,refid属性填所引用的sql标签的id值。

这样其实就形成了一个拼接,把通用的sql语句和where子句拼接起来做一个查询;只要修改sql标签中的语句就能修改所有引用了的sql代码。

编写测试代码:

public class TestSql {
    Student2DAO mapper;
    @Before
    public void init(){
        mapper = MybatisUtils.getMapper(Student2DAO.class);
    }
    @Test
    public void test1(){
        Student2 student2 = mapper.queryStudent2ById(1);
        System.out.println(student2);
    }
    @Test
    public void test2(){
        Student2 student2 = mapper.queryStudent2ByName("tang");
        System.out.println(student2);
    }
}

测试全部通过。

9.2 WHERE、IF标签

如果查询条件很多,就会在DAO文件中编写大量的查询方法,映射文件也会显得冗余。

可以考虑将所有条件查询都写进一个方法中,根据传入的值来判断查询条件。

编写查询方法:

Student2 queryStudent2(Student2 student2);

传入实体类,根据实体类的属性是否为空来判断其是否为查询条件。

编写映射文件:

<select id="queryStudent2" resultType="Student2">
    <include refid="student2_filed"/>
    <where>
        <if test="id!=null">
            id=#{id}
        </if>
        <if test="name!=null">
            or name=#{name}
        </if>
        <if test="sex!=null">
            and sex=#{sex}
        </if>
    </where>
</select>

if标签用来判断查询条件,test属性写判断条件,其中id、name、sex均是Student2的属性。

如果id不为空,就会将id=#{id}与上文拼接起来,后两者类似。

where标签等同于关键字where,为什么要用where标签呢?如果id为空,name不为空,直接拼接的效果就是“where or name=?”显然出现了sql语法错误;where标签使得关键字后紧跟的or/and省略,也就是说,如果id为空,name不为空,拼接效果就会是“where name=?”,修正了sql语法。

编写测试代码:

@Test
public void test3(){
    Student2 student=new Student2();
    student.setName("tang");
    Student2 student2 = mapper.queryStudent2(student);
    System.out.println(student2);
}

测试通过。

9.3 SET标签

在修改操作中,我们并不一定要修改数据项的所有字段,我们可能仅仅是修改名字,或者性别,就可以使用if标签实现,如下:

<update id="updateStudent2ById" parameterType="Student2">
    update t_student2
    set
    <if test="name!=null">
        name=#{name},
    </if>
    <if test="sex!=null">
        sex=#{sex}
    </if>
    where id=#{id}
</update>

如果只修改名字而性别为空,那么问题来了,拼接的结果就会是“…name=?,where…”,中间出现了逗号导致语法错误。

修改映射文件如下:

<set>
    <if test="name!=null">
        name=#{name},
	</if>
    <if test="sex!=null">
        sex=#{sex}
	</if>
    where id=#{id}
</set>

set标签等同于关键字set,然后会自动出去多余的逗号,避免语法错误。

9.4 TRIM标签

trim标签可以等价替换掉where和set标签。

替换where标签:

<select id="queryStudent2" resultType="Student2">
    <include refid="student2_filed"/>
    <trim prefix="where" prefixOverrides="or|and">
        <if test="id!=null">
            id=#{id}
        </if>
        <if test="name!=null">
            or name=#{name}
        </if>
        <if test="sex!=null">
            and sex=#{sex}
        </if>
    </trim>
</select>

prefix属性填一个前缀关键字,where表示将会在前面添加一个where关键字;prefixOverrides填写需要覆盖的关键字,如果以or或者and开头将会被覆盖。

替换set标签:

<update id="updateStudent2ById" parameterType="Student2">
    update t_student2
    <trim prefix="set" suffixOverrides=",">
        <if test="name!=null">
            name=#{name},
        </if>
        <if test="sex!=null">
            sex=#{sex}
        </if>
    </trim>
    where id=#{id}
</update>

suffixOverrides属性表示后缀覆盖,会覆盖掉sql语句后面的“,”,避免语法错误。

9.5 FOREACH标签

foreach标签可以对批量操作进行一定地支持,如批量删除或更新。

参数描述取值
collection容器类型list、array、map
open起始符(
close结束符)
separator分隔符,
index下标号从0开始,依次递增
item当前项任意名称(通过#{名称}访问)

定义批量删除方法:

Integer deleteManyStudent2ById(List<Integer> id);

批量删除:

<delete id="deleteManyStudent2ById" parameterType="int">
    <!-- delete from t_student2 where id in (x,x,x,x,...) -->
    delete from t_student2 where id in
    <foreach collection="list" open="(" close=")" separator="," item="stu2_id">
        #{stu2_id}
    </foreach>
</delete>

测试代码:

@Test
public void test5(){
    List<Integer> list= Arrays.asList(1,2);
    Integer integer = mapper.deleteManyStudent2ById(list);
    System.out.println(integer);
}

需要注意的是,如果连接有其他表,需要先删除引用的表,再删除当前表。


定义批量添加方法:

 Integer insertManyStudent2(List<Student2> student2s);

批量添加:

<insert id="insertManyStudent2" parameterType="Student2">
    <!-- insert into t_student2 value (null,x,x,x),(null,x,x,x),... -->
    insert into t_student2 value
    <foreach collection="list" separator="," item="stu2_filed">
        (null,#{stu2_filed.name},#{stu2_filed.sex})
    </foreach>
</insert>

因为id为自增,所以留null。

编写测试代码:

@Test
public void test6(){
    List<Student2> list=Arrays.asList(new Student2(null,"rui",'m'),new Student2(null,"zhang",'m'));
    Integer integer = mapper.insertManyStudent2(list);
    MybatisUtils.commit();
    System.out.println(integer);
}

十、缓存(cache)

缓存是内存中的一块存储空间,服务于某个应用程序,旨在将频繁读取的数据临时保存在内存中,便于二次快速访问。

在增删改查操作中,无疑查询操作最费时。数据库保存在本地磁盘中,服务器通过网络IO与本地数据库进行交互,本体数据库会在本地进行查询,这样会大大消耗时间;如果把经常读取而很少更新的数据放进缓存中,服务区直接读取缓存数据,那么整个系统的速度将大有改善。

10.1 一级缓存

SqlSession级别的缓存,同一个SqlSession的发起多次同构查询,会将数据保存在一级缓存中。

一级缓存是默认开启的,无需任何配置。

一级缓存实际使用的价值并不大,它只是将查询的结果存到了同一个SqlSession内,演示如下:

@Test
public void test7(){
    SqlSession sqlSession = MybatisUtils.openSession();
    Student2DAO mapper = sqlSession.getMapper(Student2DAO.class);
    mapper.queryStudent2ById(1);
    mapper.queryStudent2ById(1);
}

测试代码中调用了两次查询方法,但查询内容相同,控制台日志显示Mybatis只执行了一次SQL语句:

DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 396883763.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@17a7f733]
DEBUG [main] - ==>  Preparing: select t_student2.id,t_student2.name,t_student2.sex from t_student2 where id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1

Process finished with exit code 0

要验证缓存是否存储在同一个SqlSession内,可以额外获取一个不同的SqlSession对象,执行相同的查询方法,你可以在日志里看见Mybatis会执行多次sql语句,此处不再演示。

10.2 二级缓存

SqlSessionFactory级别的缓存,同一个SqlSessionFactory构建的SqlSession发起的多次同构查询,将会保存在二级缓存中。

一般一个项目中使用的是一个SqlSessionFactory,缓存到其中的数据是全局共享的,共享的周期会更长,所以它比一级缓存更具有使用价值。

在Mybatis配置文件中使用如下配置开启二级缓存:

<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>

true表示开启,实际上默认就是开启,所以这个设置可以省略,但要使用二级缓存,还需要配置mapper文件:

<mapper namespace="xxx">
    <cache/>
</mapper>

这个cache标签是必须的,它代表会将查询数据保存到二级缓存中。

接下来测试二级缓存,首先在工具类中添加一个获取非绑定的SqlSession方法:

//获取非绑定SqlSession
public static SqlSession getSqlSession(){
    return factory.openSession();
}

编写测试代码:

@Test
public void test8(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    SqlSession sqlSession1 = MybatisUtils.getSqlSession();
    Student2DAO mapper = sqlSession.getMapper(Student2DAO.class);
    Student2DAO mapper1 = sqlSession1.getMapper(Student2DAO.class);
    mapper.queryStudent2ById(1);
    sqlSession.close();
    mapper1.queryStudent2ById(1);
    sqlSession1.close();
}

注意运行可能会报一个java.io.NotSerializableException错误,需要在实体类上implements Serializable即可。

测试结果如下:

DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@568ff82]
DEBUG [main] - ==>  Preparing: select t_student2.id,t_student2.name,t_student2.sex from t_student2 where id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@568ff82]
DEBUG [main] - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@568ff82]
DEBUG [main] - Returned connection 90767234 to pool.
DEBUG [main] - Cache Hit Ratio [lazydog.dao.Student2DAO]: 0.5

可以观察到两次查询,Mybatis只执行了一次SQL语句;最后一行Cache Hit Ratio意思是缓存命中50%,就是说我们查了两次,其中一次是直接从缓存取的数据,所以命中率是50%。

sqlSession之所以要调用close方法,只因为在销毁sqlSession资源后Mybatis才会缓存数据,否则会等待;如果吧调用close方法删掉,你就会看见日志输出了两次sql执行语句,此处不再演示。

值得一提的细节是,缓存被用来存放经常读取而很少更新的数据,当缓存中的数据在数据库中发生了改变,那么缓存中的数据便失去了意义,甚至成为脏数据,影响正常的数据读取。

Mybatis会自动移除缓存中发生改变的数据,你可以自己测试一下(观察缓存命中率)。

十一、Druid连接池

Durid是阿里巴巴开源平台上的一个项目,整个项目由数据库连接池、插件框架和SQL解析器组成。该项目主要是为了扩展JDBC的一些限制,可以让程序员实现一些特殊的需求,比如向密钥服务请求凭证、统计SQL信息、SQL性能收集、SQL注入检查、SQL翻译等,程序员可以通过定制来实现自己需要的功能。

每一次的持久化操作都要取创建一个连接,请求结束之后连接也就废了,但向数据库请求的操作是很频繁的,创建连接也会很频繁且耗时,所以连接池是开发中必不可少的组件。

11.1 导入依赖

在pom文件中导入连接池依赖。

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.5</version>
</dependency>

11.2 创建数据源工厂

默认情况下Mybatis有自己的连接池实现PooledDataSourceFactory,它为Druid连接池工厂提供了父类,如果想替换掉Mybatis默认的连接池我们需要构建一个工厂并替换数据源。

工厂类:

public class MyDruidDataSourceFactory extends PooledDataSourceFactory {
    public MyDruidDataSourceFactory(){
        this.dataSource=new DruidDataSource();
    }
}

接下来要把工厂安装在Mybatis系统中,使Mybatis在使用连接池的时候找到Druid连接池。

修改Mybatis数据连接配置:

<dataSource type="lazydog/dataSource/MyDruidDataSourceFactory">
    <property name="driverClass" value="${jdbc.driver}"/>
    <property name="jdbcUrl" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</dataSource>

数据源改成所创建的工厂类路径,属性名也需要改成如上所示。

至此就完成了连接池的替换,可以自行运行之前的测试代码看是否正常。

十二、分页插件PageHelper

PageHelper是适用于Mybatis框架的一个分页插件,使用方式极为便捷,支持任何复杂的单表、多表分页查询操作。

PageHelper提供了多个分页操作的静态方法入口。

12.1 导入依赖

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.2.0</version>
</dependency>

12.2 修改配置文件

在Mybatis配置文件中添加如下标签,在环境标签前添加:

<plugins>
    <!-- PageHelper类所在包名 -->
    <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>

现在就可以着手做分页开发了。

编写测试代码看效果:

@Test
public void test9(){
    Student2DAO mapper = MybatisUtils.getMapper(Student2DAO.class);
    //在查询前,设置分页,查询第一页,每页两行数据
    PageHelper.startPage(1,2);
    List<Student2> list = mapper.queryAllStudent2();
    for (Student2 stu:list
        ) {
        System.out.println(stu);
    }
}

PageHelper会对之后的第一个查询,进行功能分页功能追加。

12.3 注意事项

  1. 只有PageHelper.startPage()方法后的第一个查询会执行分页。
  2. 分页插件不支持带有“for update“悲观锁的查询语句。
  3. 分页插件不支持嵌套查询,由于嵌套结果方式会导致结果集被折叠,所以无法保证分页结果数量正确。

十三、Mybatis注解开发

我们可以不写映射文件而在接口中直接添加Mybatis注解,完成CURD操作。

查询操作:

@Select("select t_student2.id,t_student2.name,t_student2.sex from t_student2 where id=#{id}")
Student2 queryStudent2ById(@Param("id") Integer id);

括号里实际上是一个字符串,所以换行需要拼接一个换行符。

其他操作类似,也并不难,所以不再演示。

接口注解定义完毕后,需要将接口全限定名注册到Mybatis的mappers配置中:

<mapper class="lazydog.dao.Student2DAO"/>

注解模式属于硬编码到.java文件中,失去了使用配置文件外部修改的优势,可结合需求使用。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值