文章目录
一、引言
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"/>
<!-- &转义& -->
<property name="url" value="jdbc:mysql://localhost:3306/userdb?useUnicode=true&characterEncoding=utf-8&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(主表):
id | name | sex | birthday |
---|---|---|---|
1001 | tang | M | 1999-01-30 |
1002 | zhang | F | 1998-10-1 |
1003 | he | M | 1999-06-06 |
t_passports(从表):
id | nationality | expire | passenger_id |
---|---|---|---|
1000001 | China | 1999-01-30 | 1001 |
1000002 | America | 2025-01-01 | 1002 |
1000003 | Korea | 2030-01-01 | 1003 |
护照表有一个外键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 多对多关系
多对多关系中,需要建立第三张关系表,当中存储两张表中的外键,并设置联合主键。
![](https://i-blog.csdnimg.cn/blog_migrate/df7610cf19860c4734db0db128404fa5.jpeg)
建立科目表和学生表,添加些许数据以供查询使用:
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)
缓存是内存中的一块存储空间,服务于某个应用程序,旨在将频繁读取的数据临时保存在内存中,便于二次快速访问。
![](https://www.hualigs.cn/image/604748c63c4f1.jpg)
在增删改查操作中,无疑查询操作最费时。数据库保存在本地磁盘中,服务器通过网络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 注意事项
- 只有PageHelper.startPage()方法后的第一个查询会执行分页。
- 分页插件不支持带有“for update“悲观锁的查询语句。
- 分页插件不支持嵌套查询,由于嵌套结果方式会导致结果集被折叠,所以无法保证分页结果数量正确。
十三、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文件中,失去了使用配置文件外部修改的优势,可结合需求使用。