概述
MyBatis
一、框架简介
1.1 框架的概念
框架是软件的半成品,完成了软件开发过程中的通用操作,程序员只需要很少或不需要进行加工就能实现特定的功能,从而简化开发过程,提高效率。
1.2 常用框架
-
MVC框架:简化了Servlet的开发步骤
- Struts2
SpringMVC
-
持久层框架:完成数据库操作的框架
- apache DBUtils
- Hibernate
- Spring JPA
MyBatis
-
平台框架:整合不同功能的框架
Spring
SSM - Spring SpringMVC MyBatis
SSH - Spring Struts2 Hibernate
1.3 MyBatis介绍
MyBatis是一个半自动的
ORM
框架: 半自动:因为Hibernate是一个全自动的框架
ORM
:Object Relational Mapping,对象关系映射,将Java中的一个对象于数据表中的一行记录一一对应。ORM框架提供了实体类于数据表的映射关系,通过映射文件的配置,实现对象的持久化。
- MyBatis的前身是
iBtis
,是由apache基金维护的一个开源项目 - 在2010年时,
iBatis
迁移到了(代码托管)Google Code,正式更名为MyBatis
- 在2013年时,MyBatis迁移到了github
MyBatis
的特点:- 支持自定义SQL,支持存储过程
- 对原有的JDBC进行了封装,几乎消除了所有的JDBC代码(打开连接,加载执行SQL…),开发者只需要关注SQL
- 支持XML和注解配置方式自动完成ORM操作,实现结果映射
二、MyBatis框架部署
框架部署,就是将框架引入项目中
2.1 创建Maven项目
- Java工程
- Web工程
2.2 添加MyBatis依赖
- 在
pom.xml
中添加依赖,以下两个依赖都需要- Mysql Driver
- MyBatis
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
2.3 创建MyBatis配置文件
在maven项目的main文件夹下有一个resources文件夹,在此文件夹下新建一个mybatis的配置文件。可以先添加
Mybatis
配置文件模板。
<configuration>
<!-- 在environments中配置数据库的连接信息 -->
<!-- 在environments标签中可以定义多个environment标签,每个environment标签可以定义一套连接配置 -->
<!-- default属性,指定使用哪一套配置 -->
<environments default="development">
<environment id="development">
<!-- transactionManager标签用于配置数据库的管理方式 -->
<transactionManager type="JDBC"></transactionManager>
<!-- dataSource标签就是用来配置数据库连接信息的 -->
<dataSource type="POOLED">
<property name="driver" value="${database.driver}"/>
<property name="url" value="${database.url}"/>
<property name="username" value="${database.username}"/>
<property name="password" value="${database.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource=""></mapper>
</mappers>
</configuration>
三、MyBatis框架使用
案例:学生信息数据库操作
3.1 创建数据表
create table tb_students(
sid int primary key auto_increment,
stu_num char(5) not null UNIQUE,
stu_name VARCHAR(20) not null,
stu_gender char(2) not null,
stu_age int not null
)
3.2 创建实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Student {
private int stuId;
private String stuNum;
private String stuName;
private String stuGender;
private String stuAge;
}
3.3 创建DAO接口,定义方法
public interface StudentDAO {
public int insertStudent(Student s);
public int deleteStudent(String stuNum);
}
3.4 创建接口映射文件
- 在
resources
目录下新建名为mappers
的文件夹 - 在
mappers
中新建名为StudentMapper.xml
的配置文件,需要根据模板 - 在映射文件中对DAO中定义的方法进行实现
<!--映射文件要与相对应的mapper接口通过namespace属性进行关联-->
<mapper namespace="com.gsjt.dao.StudentDAO">
<!--id为mapper类中对应方法名,resultType为定义的接收类型,一般为对应实体类-->
<select id="" resultType="">
</select>
<!--id为mapper类中方法名-->
<insert id="insertStudent">
insert into tb_students(stu_num, stu_name, stu_gender, stu_age)
values (#{stuNum}, #{stuName}, #stuGender, #{stuAge})
</insert>
<delete id="deleteStudent">
delete from tb_students where stu_name = #{stuNum}
</delete>
</mapper>
3.5 将映射文件添加到主配置文件
<mappers>
<mapper resource="mappers/StudentMapper.xml"></mapper>
</mappers>
四、单元测试
public class StudentDAOTest {
@Test
public void insertStudent() {
try {
// 加载mybatis配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
// 会话(连接)工厂,参数为连接信息
SqlSessionFactory factory = builder.build(is);
SqlSession sqlSession = factory.openSession();
StudentDAO studentDAO = sqlSession.getMapper(StudentDAO.class);
System.out.println(studentDAO);
// 插入方法
int i = studentDAO.insertStudent(new Student(1, "10002", "李四", "男", 22));
// 手动提交的事务
sqlSession.commit();
System.out.println("插入操作返回的结果, " + i);
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void deleteStudent() {
}
}
五、MyBatis的增删改查
案例:学生信息的增删改查
5.1 添加
略,上一节已经演示
5.2 删除
根据学号进行删除操作
-
在
StudentDAO
中定义删除方法 -
在
StudentMapper.xml
中对接口方法进行“实现”,使用deldete
标签 -
测试类中编写测试代码
@Test public void deleteStudent() { try { InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(in); SqlSession session = factory.openSession(); StudentDAO studentDAO = session.getMapper(StudentDAO.class); int i = studentDAO.deleteStudent("10002"); session.commit(); System.out.println("删除操作的结果, " + i); } catch (Exception e) { e.printStackTrace(); } }
5.3 修改操作
根据学号(主键)修改其他字段信息
-
添加接口方法
public int updateStudent(Student s);
-
添加
mapper
映射<update id="updateStudent"> update tb_students set stu_name = #{stuName}, stu_gender = #{stuGender}, stu_age = #{stuAge} where stu_num = #{stuNum} </update>
-
测试方法
@Test public void testUpdateStudent() { try { InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(in); SqlSession session = factory.openSession(); StudentDAO studentDAO = session.getMapper(StudentDAO.class); int i = studentDAO.updateStudent(new Student(0, "10001", "赵六", "女", 26)); session.commit(); assertEquals(1, i); } catch (Exception e) { e.printStackTrace(); } }
5.4 查询所有
需要注意查询出来的结果和普通Java类的映射关系,有两种写法。
注意:查询是不需要事务的,写不写无所谓。
-
给查出来的字段起别名,并使用指定类名
<!-- resultType指定返回结果封装的实体类 --> <!-- resultSets指定当前操作发牛的集合类型类型,在接口中方法返回值有的情况下可以省略 --> <select id="searchAllStudents" resultType="com.gsjt.pojo.Student"> select sid stuId, stu_num stuNum, stu_name stuName, stu_gender stuGender, stu_age stuAge from tb_students; </select>
-
使用
resultMap
,推荐使用这种,可复用。<!-- 用于定义ORM --> <resultMap id="studentMap" type="com.gsjt.pojo.Student"> <id column="sid" property="stuId"/> <id column="stu_num" property="stuNum"/> <id column="stu_name" property="stuName"/> <id column="stu_gender" property="stuGender"/> <id column="stu_age" property="stuAge"/> </resultMap> <!-- resultMap用于引用一个实体映射关系 --> <select id="searchAllStudents" resultMap="studentMap"> select sid, stu_num, stu_name, stu_gender, stu_age from tb_students; </select>
5.5 查询单条记录
<select id="queryStudent" resultMap="studentMap">
select sid, stu_num, stu_name, stu_gender, stu_age
from tb_students
where stu_num = #{param}
</select>
@Test
public void testQueryStudent() {
try {
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
SqlSession session = factory.openSession();
StudentDAO studentDAO = session.getMapper(StudentDAO.class);
Student s = studentDAO.queryStudent("10001");
assertNotNull(s);
System.out.println(s.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
5.6 多参数查询
注意
:在MyBatis进行条件查询的时候,1、操作方法只有一个简单类型或者子字符串类型的参数,在
mapper
配置的时候可以直接通过#{}
任意参数直接获取;2、如果操作方法,有一个对象类型的参数,在
mapper
配置的时候可以直接使用#{attrname}
获取属性的值(attrname
必须是该参数对象的属性);3、如果操作方法有一个
map
类型的参数,在Mapper
配置中可以直接通过#{key}
获取对应key的value;4、操作方法有多个参数,使用
@Param
注解指定别名.
注意
:如果DAO操作方法没有通过@Param
指定参数别名,在SQL语句中可以通过arg0,arg1...
或者param1, param2...
来获取参数。
public List<Student> listStudentsByPage(@Param("start") int start,
@Param("pageSize") int pageSize);
<select id="listStudentsByPage" resultMap="studentMap">
select sid, stu_num, stu_name, stu_gender, stu_age
from tb_students
limit #{start}, #{pageSize}
</select>
@Test
public void testListStudentByPage() {
try {
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
SqlSession session = factory.openSession();
StudentDAO studentDAO = session.getMapper(StudentDAO.class);
List<Student> s = studentDAO.listStudentsByPage(0, 10);
assertNotNull(s);
s.forEach(item -> System.out.println(item.toString()));
} catch (IOException e) {
e.printStackTrace();
}
}
5.7 查询记录总数
public int countStudents();
通过
resultMap
指定当前的返回值类型为int,不然会报错
<select id="countStudents" resultType="int">
select count(1) from tb_students
</select>
5.8 添加操作,回填自动生成的主键
添加操作时,new了一个Student对象,当插入时,stuId应该是数据库中自增的那个主键,应该回填给这个student对象。
<!-- useGeneratedKeys是否需要回填,keyProperty设置回填属性 -->
<insert id="insertStudent" useGeneratedKeys="true" keyProperty="stuId">
insert into tb_students(stu_num, stu_name, stu_gender, stu_age)
values (#{stuNum}, #{stuName}, #{stuGender}, #{stuAge})
</insert>
useGeneratedKeys
是否需要回填keyProperty
设置回填属性
六、mybatis工具类封装
public class MyBatisUtil {
private static SqlSessionFactory factory;
private static final ThreadLocal<SqlSession> local = new ThreadLocal<SqlSession>();
static {
try {
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
factory = new SqlSessionFactoryBuilder().build(in);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession() {
SqlSession sqlSession = local.get();
if (sqlSession == null) {
sqlSession = factory.openSession();
local.set(sqlSession);
}
return factory.openSession();
}
public static <T extends Object>T getMapper(Class<T> c) {
SqlSession sqlSession = getSqlSession();
return sqlSession.getMapper(c);
}
}
七、事务管理
SqlSession
对象
getMapper(DAO.class)
:获取Mapper(DAO接口的实例)- 事务管理:提供事务管理方法
7.1 手动提交事务
@Test
public void deleteStudent() {
SqlSession session = MyBatisUtil.getSqlSession();
try {
StudentDAO studentDAO = session.getMapper(StudentDAO.class);
int i = studentDAO.deleteStudent("10002");
session.commit(); // 提交
System.out.println("删除操作的结果, " + i);
} catch (Exception e) {
session.rollback(); // 回滚
e.printStackTrace();
}
}
7.2 自动提交事务
factory.openSession()
方法可以传一个Boolean类型的参数,表示是否自动提交事务。默认是false。
但是此时如果在程序中有多个数据库操作,若使用自动提交,则第一个操作完成后就自动提交了。此时建议手动管理。
八、MyBatis主配置文件
mybatis-config.xml 是 MyBatis主配置文件
注意
:配置文件中的标签必须有序,按照如下顺序:properties, settings, typeAliases, typeHandlers, objectFactory, objectWrapperFactory, plugins, environments, databaseIdProvider, mappers
8.1 properties
标签
用于设置键值对,或者加载属性文件。
<property name ="" value=""/>
-
在resources文件夹下创建
jdbc.properties
文件,键值对配置如下:mysql_driver = com.mysql.jdbc.Driver mysql_url = jdbc:mysql://localhost:3306/testdb?characterEncoding=utf-8 mysql_username = root mysql_password = 123456
-
主配置文件如下,
properties
标签引入之后,里面可以根据键值对取出数据<configuration> <properties resource="jdbc.properties"></properties> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"></transactionManager> <dataSource type="POOLED"> <property name="driver" value="${mysql_driver}"/> <property name="url" value="${mysql_url}"/> <property name="username" value="${mysql_username}"/> <property name="password" value="${mysql_password}"/> </dataSource> </environment> </environments> <mappers> <mapper resource=""></mapper> </mappers> </configuration>
8.2 settings
标签
设置mybatis工作时的一些属性
<settings>
<setting name="cacheEnable" value="true"/> <!-- 二级缓存 -->
<setting name="lazyLoadingEnable" value="true"/> <!-- 延迟加载 -->
</settings>
8.3 typeAlias
标签
<!-- 用于给实体类取别名,在映射文件中可以直接使用别名来替代实体类的全限定名 -->
<typeAliases>
<typeAlias type="com.gsjt.pojo.Student" alias="Student"></typeAlias>
</typeAliases>
8.4 plugins
标签
用于配置mybatis插件,例如分页插件。
8.5 environments
标签
配置数据库连接信息
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${mysql_driver}"/>
<property name="url" value="${mysql_url}"/>
<property name="username" value="${mysql_username}"/>
<property name="password" value="${mysql_password}"/>
</dataSource>
</environment>
</environments>
environment
标签,用于配置一个单独的数据库连接信息transactionManager
的type
属性有两个值可选:- JDBC:用JDBC方式自己在代码中进行事务管理
- MANAGED:事务交给容器管理,连接不自己手动处理
dataSource
标签,配置数据库连接信息,type
属性的几个取值:- POOLED
- UNPOOLED
- JNDI
8.6 mappers
标签
用于载入项目中的映射配置(映射文件,DAO注解)
<mappers>
<mapper resource="mappers/StudentMapper.xml"></mapper>
</mappers>
九、mapper
文件配置
9.1 MyBatis Mapper文件的初始化过程
9.2 Mapper文件规范
-
mapper文件的根标签必须为
mapper
,mapper
有个属性namespace
指定实现哪个接口 -
insert
声明添加操作常用属性:
- id,绑定接口方法名
- parameterType,用于指定接口中对应方法的参数类型(可省略)
- useGeneratedKeys,添加操作是否需要主键回填
- keyProperty,指定回填的主键绑定到对象的哪个属性
- timeout,设置此操作的超时时间,如果不设置就一直等待
主键回填的另外一种方式:使用selectKey
标签。
-
delete
声明删除 -
update
修改 -
select
查询操作,属性最多的操作常用属性:
- resultType,指定当前sql执行返回的类型
- resultMap,指定数据表和实体类之间的对应关系
- useCache,是否开启二级缓存
- id
- timeout
- parameterType
-
resultMap
指定字段映射关系<!-- 用于定义ORM --> <resultMap id="studentMap" type="com.gsjt.pojo.Student"> <id column="sid" property="stuId"/> <id column="stu_num" property="stuNum"/> <id column="stu_name" property="stuName"/> <id column="stu_gender" property="stuGender"/> <id column="stu_age" property="stuAge"/> </resultMap>
-
cache
指定缓存属性配置<cache type="" size="" readOnly="false"/>
缓存的配置详见缓存章节
-
sql
和include
标签,相当于服用,sql
标签定义sql语句片段,include
引入这个片段。<sql id="test">sid, stu_name, stu_gender</sql> <select id="" resultMap =""> select <include refid="test"> from tb_students </select>
十、分页插件
分页插件是一个独立于MyBatis框架之外的第三方插件
PageHelper
10.1 添加分页插件依赖
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
10.2 配置问价
在mybatis的主配置文件中通过
plugins
标签进行配置
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
10.3 使用
@Test
public void testPageHelper() {
StudentDAO studentDAO = MyBatisUtil.getMapper(StudentDAO.class);
PageHelper.startPage(2, 4);
List<Student> students = studentDAO.listByPageHelper();
PageInfo<Student> pageInfo = new PageInfo<Student>(students);
students.forEach(item -> System.out.println(item.toString()));
System.out.println("--------------------------------------------------");
pageInfo.getList().forEach(item -> System.out.println(item.toString()));
}
十一、关联映射
11.1 实体关系
实体关系——数据实体,实体关系事知数据与数据之间的关系
例如:用户和角色,房屋和漏洞,订单和商品
实体关系分为以下四种:
-
一对一:人和身份证,学生和学号,用户基本信息和用户详情
数据表关系:主键关联(用户表主键和详情主键相同,表示匹配的数据)
-
一对多:班级和学生
-
多对一:学生和班级
数据表关系:在多的那一端添加外键和一的那一端主键关联
-
多对多:用户和角色,学生和社团
数据表关系:建立第三章关系表分别和原两张表的主键进行关联
11.2 创建web项目,引入mybatis
11.3 一对一关系
实例:用户——详情
11.3.1 数据表
-- 用户信息表
create table users(
user_id int primary key auto_increment,
user_name varchar(20) not null unique,
user_pwd varchar(20) not null,
user_realname varchar(20) not null,
user_img varchar(100) not null
);
-- 用户详情表
create table details(
detail_id int primary key auto_increment,
user_addr varchar(50) not null,
user_tel char(11) not null,
user_desc varchar(200),
uid int not null unique,
-- constraint users foreign key(uid) reference users(user_id)
);
11.3.2 创建实体类
/* User.java */
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class User {
private int userId;
private String userName;
private String userPwd;
private String userRealName;
private String userImg;
// 内部对象
private Detail detail;
}
/* Detail.java */
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Detail {
private int detailId;
private String userAddr;
private String userTel;
private String userDesc;
private int userId;
}
11.3.3 插入
插入时需要保证原子性,插入users表的同时也要插入details表。并且在逻辑上使用外键。需要插入users表的时候使用主键回填。
<sql id="allAttrs">
user_name, user_pwd, user_realname, user_img
</sql>
<insert id="insertUser" useGeneratedKeys="true" keyProperty="userId">
insert into users(<include refid="allAttrs"/>)
values (#{userName}, #{userPwd}, #{userRealName}, #{userImg})
</insert>
<!-- detail的mapper -->
<sql id="allAttrs">
user_addr, user_tel, user_desc, user_id
</sql>
<insert id="insertDetail">
insert into details(<include refid="allAttrs"/>)
values (#{userAddr}, #{userTel}, #{userDesc}, #{userId})
</insert>
@Test
public void testUserInsert() {
User u = new User(0, "nickname", "wadwa", "赵六", "01.jpg", null);
Detail d = new Detail(0, "武汉", "18600011125", "这是一条签名", 0);
SqlSession sqlSession = MyBatisUtil.getSqlSession();
try {
UserDAO userDAO = sqlSession.getMapper(UserDAO.class);
int i = userDAO.insertUser(u);
System.out.println("新用户插入, " + i);
d.setUserId(u.getUserId()); // 设置逻辑外键
DetailDAO detailDAO = sqlSession.getMapper(DetailDAO.class);
int j = detailDAO.insertDetail(d);
System.out.println("详情插入, " + j);
sqlSession.commit(); // 提交事务
} catch (Exception e) {
e.printStackTrace();
sqlSession.rollback(); // 回滚事务
}
}
11.3.4 一对一关联查询
难点在于,关联的映射,有两种映射方式
-
表连接查询,将内部类属性直接映射
<sql id="allAttrs"> user_name, user_pwd, user_realname, user_img </sql> <resultMap id="userMap" type="com.gsjt.pojo.User"> <id column="user_id" property="userId"/> <result column="user_name" property="userName"/> <result column="user_pwd" property="userPwd"/> <result column="user_realname" property="userRealName"/> <result column="user_img" property="userImg"/> <result column="detail_id" property="detail.detailId"/> <result column="user_addr" property="detail.userAddr"/> <result column="user_tel" property="detail.userTel"/> <result column="user_desc" property="detail.userDesc"/> <result column="user_id" property="detail.userId"/> </resultMap> <select id="queryUser" resultMap="userMap"> select u.user_id, <include refid="allAttrs"/>, detail_id, user_addr, user_tel, user_desc from users u inner join details d on u.user_id = d.user_id where u.user_name = #{username}; </select>
-
使用子查询
-
在另外一张表中使用查询
<sql id="allAttrs"> user_addr, user_tel, user_desc, user_id </sql> <resultMap id="detailMap" type="com.gsjt.pojo.Detail"> <id column="detail_id" property="detailId"/> <result column="user_addr" property="userAddr"/> <result column="user_tel" property="userTel"/> <result column="user_desc" property="userDesc"/> <result column="user_id" property="userId"/> </resultMap> <select id="queryDetailByUid" resultMap="detailMap"> select detail_id, <include refid="allAttrs"/> from details where user_id = #{userId} </select>
-
在主表中使用子查询,借助
assosiation
标签<resultMap id="userMapUseSubQuery" type="com.gsjt.pojo.User"> <id column="user_id" property="userId"/> <result column="user_name" property="userName"/> <result column="user_pwd" property="userPwd"/> <result column="user_realname" property="userRealName"/> <result column="user_img" property="userImg"/> <association property="detail" select="com.gsjt.dao.DetailDAO.queryDetailByUid" column="user_id"/> </resultMap> <select id="queryUserUseSubQuery" resultMap="userMapUseSubQuery"> select user_id, <include refid="allAttrs"/> from users where user_name = #{username} </select>
-
11.4 一对多关联
案例:班级信息(1) —— 学生信息(n)11.4.1
11.4.1 创建表
create table classes(
cid int primary key auto_increment,
cname varchar(20) not null unique,
cdesc varchar(100)
);
create table students(
sid char(5) primary key,
sname varchar(20) not null,
sage int not null,
scid int not null
);
11.4.2 实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Clazz {
private int classId;
private String className;
private String classDesc;
private List<Student> stus;
}
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Student {
private String studentId;
private String stuName;
private int stuAge;
private int stuCid;
}
11.4.3 关联查询
当查询一个班级的时候,要关联查询出来这个班级所有的学生
也有两种实现方式
-
连接查询
<resultMap id="classMap" type="Clazz"> <id column="cid" property="classId"/> <result column="cname" property="className"/> <result column="cdesc" property="classDesc"/> <!-- Clazz对象的stus属性是一个List集合,需要使用collection标签 --> <!-- collection标签的property属性表示绑定到对象的哪个属性,ofType属性表示集合内的元素类型 --> <collection property="stus" ofType="Student"> <result column="sid" property="stuId"/> <result column="sname" property="stuName"/> <result column="sage" property="stuAge"/> </collection> </resultMap> <select id="queryClass" resultMap="classMap"> select cid, cname, cdesc, sid, sname, sage from classes c inner join students s on c.cid = s.scid where c.cid = #{classId}; </select>
-
子查询
<resultMap id="classMap" type="Clazz"> <id column="cid" property="classId"/> <result column="cname" property="className"/> <result column="cdesc" property="classDesc"/> <collection property="stus" select="com.gsjt.dao.StudentDAO.listStudentsByCid" column="cid"/></resultMap> <select id="queryClass" resultMap="classMap"> select cid, cname, cdesc from classes where cid = #{classId} </select>
同时也要配置子查询中的查询接口和mapper。
11.5 多对一关联
实例:学生(n) —— 班级(1)
当查询一个学生的时候,同时查询出对应的班级对象。
11.5.1 实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Clazz {
private int classId;
private String className;
private String classDesc;
// private List<Student> stus;
}
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Student {
private String stuId;
private String stuName;
private int stuAge;
// private int stuCid;
private Clazz clazz;
}
11.5.2 关联查询
- 连接查询:于一对一关系查询差不多
- 子查询:于一对一关系查询差不多
11.6 多对多关联
案例:学生和课程
11.6.1 创建数据表
其中需要创建第三章关系表
-- 学生表如上
-- 课程表
create table courses(
cources_id int primary key auto_increment,
cource_name varchar(20) not null
);
-- 选课信息表/成绩表
create table grades(
sid char(5) not null,
cid int not null,
score int not null
);
11.6.2 关联查询
查询学生时,同时查询学生选择的课程
根绝课程编号查询课程时,同时查询选择这门课的学生
十二、动态SQL
交友网站,电商平台都有筛选功能
Mybatis提供了动态SQL的配置方式来实现多条件查询
12.1 什么是动态SQL
根据查询条件动态的完成SQL的拼接
12.2 标签
-
if
:满足条件则拼接,否则不拼接<select id="findActiveBlogWithTitleLike" resultType="Blog"> SELECT * FROM BLOG WHERE 1 = 1 <if test="title != null"> AND title like #{title} </if> <if test="author != null and author.name != null"> AND author_name like #{author.name} </if> </select>
-
choose
、when
、otherwise
:类似于java中的switch,从多个条件中选择一个使用<select id="findActiveBlogLike" resultType="Blog"> SELECT * FROM BLOG WHERE 1 = 1 <choose> <when test="title != null"> AND title like #{title} </when> <when test="author != null and author.name != null"> AND author_name like #{author.name} </when> <otherwise> AND featured = 1 </otherwise> </choose> </select>
-
where
标签:用于定义where语句<select id="findActiveBlogLike" resultType="Blog"> SELECT * FROM BLOG <where> <if test="state != null"> state = #{state} </if> <if test="title != null"> AND title like #{title} </if> <if test="author != null and author.name != null"> AND author_name like #{author.name} </if> </where> </select>
where
标签元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。 -
trim
:如果where
标签定义的字句不满足要求,也可以自定义trim
元素来定制where
元素的功能。比如和where
子句等价的自定义trim
元素为:<trim prefix="WHERE" prefixOverrides="AND |OR "> ... </trim>
prefixOverrides
属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有prefixOverrides
属性中指定的内容,并且插入prefix
属性中指定的内容。 -
set
标签:用于动态更新语句。set
元素可以动态的包含需要的列,忽略其他更新的列:<update id="updateAuthorIfNecessary"> update Author <set> <if test="username != null">username=#{username},</if> <if test="password != null">password=#{password},</if> <if test="email != null">email=#{email},</if> <if test="bio != null">bio=#{bio}</if> </set> where id=#{id} </update>
等价于:
<trim prefix="SET" suffixOverrides=","> ... </trim>
-
forEach
:集合遍历(在构建IN
子句时常用)<select id="selectPostIn" resultType="domain.blog.Post"> SELECT * FROM POST P WHERE ID in <foreach item="item" index="index" collection="list" open="(" separator="," close=")"> #{item} </foreach> </select>
-
bind
:允许创建一个变量,在表达式中动态绑定<select id="selectBlogsLike" resultType="Blog"> <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" /> SELECT * FROM BLOG WHERE title LIKE #{pattern} </select>
-
script
:要在带注解的映射器接口类中使用动态的SQL,可以使用script
元素@Update({"<script>", "update Author", " <set>", " <if test='username != null'>username=#{username},</if>", " <if test='password != null'>password=#{password},</if>", " <if test='email != null'>email=#{email},</if>", " <if test='bio != null'>bio=#{bio}</if>", " </set>", "where id=#{id}", "</script>"}) void updateAuthorValues(Author author);
12.3 #{} 和 ${}
${key}
表示获取参数,先获取参数的值拼接到sql语句中,然后再执行该sql。可能引起SQL注入的问题;#{key}
表示获取参数,先完成sql语句的编译(预编译
),预编译之后再将获取的参数设置到SQL中。即先编译成?
占位符。这样可以避免SQL注入的问题。
注意
:在使用${key}
的时候,默认去参数时会当作是对象来取,传参的时候可以用参数对象或者HashMap,如果是普通的值则必须要声明参数类型,在select标签中要添加parameterType
属性,而且在传参的时候必须加上@Param
才能获取到,哪怕只有一个参数。
<select id="searchMember" parameterType="java.lang.String" resultMap="...">
select * from ...
where name like '%${keyword}%'
</select>
pubilc List<...> ambigousSearch(@Param("keyword") String keyword);
十三、MyBatis日志配置
MyBatis作为一个封装好的ORM框架,其运行过程没有办法进行跟踪,为了当开发者了解其执行流程及每个执行步骤完成所有的工作,MyBatis框架本身集成了
log4j
日志框架,对运行的过程进行记录跟踪。我们只需要对MyBatis进行相关的日志配置,就可以看到MyBatis运行过程中的日志信息。
13.1 添加日志框架依赖
<!-- log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
13.2 添加日志配置文件
-
在
resources
目录下创建名为log4j.properties
的文件,必须在该文件夹下创建该名称的文件,因为MyBatis自动加载。 -
在
log4j.properties
文件中配置日志输出的方式。 这是MyBatis官方的配置
# 声明日志的输出级别及输出方式 log4j.rootLogger=DEBUG,stdout log4j.logger.org,mybatis.example.BlogMapper=TRACE # Console output... log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout # 定义打印格式 %t是线程名称 %5p是日志级别 log4j.appender.stdout.layout.ConversionPattern=[%t] %5p - %n%m
13.3 日志级别
在使用日志框架进行输出日志信息的时候,会根据输出的日志信息的重要程度分为5个级别。由宽松到严格。
日志级别 | 说明 |
---|---|
DEBUG | 输出调试信息 |
INFO | 输出提示性信息 |
WARN | 输出警告信息 |
ERROR | 一般性错误信息 |
FATAL | 致命性错误信息 |
十四、数据库连接池配置-整合Druid
连接池:连接池就是用于存储连接对象的一个容器,而容器就是一个集合,且必须是线程安全的,即两个线程不能拿到同一个连接对象。同时还要具备队列的特性:先进先出原则。
使用连接池的好处:避免频繁创建和关闭数据库连接造成的开销,节省系统资源。
MyBatis作为一个ORM框架,在进行数据库操作的时候是需要和数据库进行连接的,MyBatis支持基于数据库的连接池的创建方式。
当我们配置MyBatis数据源的时候,只配置了
dataSource
标签的type
属性值为POOLED
时,就可以使用MyBatis内置的连接池管理连接。 如果我们想要使用第三方的数据库连接池就需要自己配置。
14.1 常见的连接池
-
JDBC
-
DBCP
-
C3P0
-
Druid:性能比较好,提供了比较便捷的监控系统,在企业中使用的比较多,阿里开源
-
HiKari:性能最好
14.2 添加依赖
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
14.3 创建Druid连接池工厂
创建一个类继承
PooledDataSourceFactory
,在无参构造器中设置dataSource
为DruidDataSource
public class DruidDataSourceFactory extends PooledDataSourceFactory {
public DruidDataSourceFactory() {
this.dataSource = new DruidDataSource();
}
}
14.4 将Druid连接池工厂配置给MyBatis数据源
在MyBatis主配置文件中,使用
DruidDataSourceFactory
。
<environment id="DruidTest">
<transactionManager type="JDBC"></transactionManager>
<!-- POOLED使用的是MyBatis内部的连接池 -->
<!-- MyBatis需要一个连接池工厂,这个工厂可以产生数据库连接池PooledDataSourceFactory -->
<!-- DruidDataSourceFactory继承自PooledDataSourceFactory -->
<dataSource type="com.gsjt.utils.DruidDataSourceFactory">
<property name="driverClass" value="${mysql_driver}"/>
<property name="jdbcUrl" value="${mysql_url}"/>
<property name="username" value="${mysql_username}"/>
<property name="password" value="${mysql_password}"/>
</dataSource>
</environment>
同时,Druid需要的参数是
driverClass
和jdbcUrl
这和POOLED
模式不一样。
十五、MyBatis缓存
MyBatis是基于JDBC的封装,使得数据库操作更加便捷,MyBatis除了对JDBC进行操作步骤进行封装之外也对其性能进行了优化:
- 在MyBatis中引入了缓存机制,用于提升MyBatis的检索效率
- 在MyBatis中引入了延迟加载机制,用于减少对数据库的不必要的访问
15.1 缓存的工作原理
- 检查缓存中是否存在要查询的数据;
- 如果存在,则直接从缓存获取数据并返回,减少了访问数据库的次数,大大提升效率;
- 如果不存在则从数据库中查询,查询之后将数据返回,同时存储到缓存中,一共下次查询使用。
15.2 MyBatis中的缓存
MyBatis中的缓存分为一级缓存和二级缓存
15.2.1 一级缓存
一级缓存也叫做
SqlSession
级缓存,为每个SqlSession
单独分配缓存内存,无需手动开启即可直接使用;多个SqlSession的缓存是不共享的
。
一级缓存的特性:
- 如果多次查询使用的是同一个
SqlSession
对象,则第一次查询之后数据会存放到缓存,后续的查询则直接访问缓存; - 如果第一次查询之后,对查询出来的对象进行修改,此修改会影响到缓存,第二次查询会直接访问缓存,造成查询出来的结果于数据库不一致。
- 当想要跳过缓存直接查询数据库的时候,可以通过
sqlSession.clearCache()
来清除当前SqlSession
对象的缓存。 - 如果第一次查询之后,第二次查询之前使用当前的
sqlSession
进行更新,则缓存失效,第二次查询直接访问数据库。
存在的问题
- 第一次查询之后,进行了修改操作,数据库已经被修改,但是第二次查询的时候依然显示修改前的数据
- 分析:修改和查询不是同一个线程,因此使用不同的
dao
对象(使用了不同的SqlSession
),因此修改不会导致查询操作的缓存失效; - 解决思路有两种
- 让查询和修改使用相同的
SqlSession
对象(不太合理); - 让每次进行查询操作之后清空缓存。
- 让查询和修改使用相同的
- 分析:修改和查询不是同一个线程,因此使用不同的
15.2.2 二级缓存
二级缓存也称为
SqlSessionFactory
级的缓存。通过同一个factory
对象获取的SqlSession
可以共享二级缓存;在应用服务器中SqlSessionFactory
是 单例的,因此二级缓存的数据可以全局共享。
二级缓存特性:
-
二级缓存默认没有开启,需要在
mybatis-config.xml
中的setting
标签中进行开启,<settings> <setting name="cacheEnabled" value="true"></setting> </settings>
另外在
mapper
文件内只要使用了chche
标签则,则开启了二级缓存。这个标签有一些参数,eviction
设置缓存回收策略,取值LRU(默认),FIFO,SOFT,WEAK。flushInterval
设置刷新间隔,默认是不清空。readOnly
是否只读。size
指定大小。type
指定自定义缓存的全类名,实现Cache接口即可。<cache></cache>
-
二级缓存只能缓存实现了序列化接口的对象;
15.2.3 缓存查询数据
-
先判断二级缓存是否开启,如果没开启,再判断一级缓存是否开启,如果没开启,直接查数据库
-
如果一级缓存关闭,即使二级缓存开启也没有数据,因为二级缓存的数据从一级缓存获取
-
一般不会关闭一级缓存
-
二级缓存默认不开启
-
如果二级缓存关闭,直接判断一级缓存是否有数据,如果没有就查数据库
-
如果二级缓存开启,先判断二级缓存有没有数据,如果有就直接返回;如果没有,就查询一级缓存,如果有就返回,没有就查询数据库;
综上所述:先查二级缓存,再查一级缓存,再查数据库;即使在一个
sqlSession
中,也会先查二级缓存;一个namespace
中的查询更是如此;
十六、延迟加载
延迟加载是指,如果在MyBatis执行了子查询(至少查询两次以上),默认只执行第一次查询,当用到子查询的查询结果时,才会触发子查询的执行。如果没有使用子查询的结果,则子查询不会进行。
需要配置MyBatis的主配置文件
<settings>
<setting name="lazyLoadingEnabled" value="true"/><!--延迟加载/懒加载-->
<setting name="aggressiveLazyLoading" value="false"/><!--积极加载/预加载-->
</settings>
最后
以上就是时尚心情为你收集整理的Mybatis学习笔记MyBatis的全部内容,希望文章能够帮你解决Mybatis学习笔记MyBatis所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复