Mybatis框架
共四部分
- 第一部分:mybatis入门
- mybatis概述
- mybatis环境搭建
- mybatis入门案例
- 自定义mybatis框架
- 第二部分:mybatis的基本使用
- 单表CRUD操作
- mybatis的参数和返回值
- mybatis的dao的编写
- mybatis配置细节
- 几个标签的使用
- 第三部分:mybatis的深入和多表
- mybatis的连接池
- mybatis的事务控制以及设计的方法
- mybatis的多表查询
- 一对多/多对一
- 多对多
- 第四部分:mybatis缓存和注解开发
- mybatis中的加载时机(也就是查询时机)
- mybatis中的一级缓存和二级缓存
- mybatis的注解开发
- 单表crud
- 多表查询
第一部分:mybatis入门
1. 为什么要框架
- 框架:是我们软件开发中的一套解决方案,不同的框架解决的是不同的问题,比如mybatis解决的就是持久层的问题
- 使用框架的好处:框架封装了很多的细节,使开发者可以使用极简的方式实现功能,提高开发效率
2. 三层架构
- 表现层:展示数据
- 业务层:处理业务需求
- 持久层:是和数据库交互的
3. 持久层技术解决方案
- JDBC(最底层)
- 三个对象
- connection
- PreparedStatement
- ResultSet
- 过程
- 加载数据库驱动Class.forName
- 通过驱动管理类获取数据库链接connection=DriverManager.getConnection("")
- 定义sql语句
- 获取预处理statement,对sql进行处理,返回结果connection.prepareStatement(sql)
- 释放资源
- 三个对象
- Spring的JdbcTemplate:spring中对jabc的简单封装
- apache的DBUtils:和spring的jdbcTemplate很像
- 总结:
以上的都不是框架,jdbc是规范,spring的jdbcTemplate和DBUtils都只是工具类,并没有完整的解决方案,比如上面的jdbc过程很繁琐,其实我们开发,只需要sql语句就可以,别的操作都是很繁琐的,所以要选择一门技术将哪些繁琐的工作封装起来,让我们把更多的精力放到实现功能解决问题上去
4. Mybatis框架概述
- mybatis是一个优秀的基于java的持久层框架,他内部封装了jdbc,使得开发者只需要关注sql
- mybatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。
- 采用ORM思想解决了实体和数据库映射的问题,对jdbc进行了封装,屏蔽了jdbc api底层访问细节,使我们不用与jdbc api打交道,就可以完成对数据库的持久化操作。使我们能够更好掌握框架运行的内部过程,并且有更好的体验
- ORM思想:Object Relational Mapping 对象关系映射
- 简单的说,就是把数据库表和实体类以及实体类的属性对应起来,让我们可以实现操作实体类就是操作数据库表
5. mybatis环境搭建
5.1 环境搭建的步骤
- 创建maven工程并导入坐标(也就是设置pom.xml文件)
- 创建实体类和dao接口,dao接口里面包含对实体类的操作,比如说查询所有findAll()
- 创建Mybatis的主配置文件:SqlMapConfig.xml
- 创建映射配置文件:IUserDao.xml
5.2 环境搭建具体实现
- 创建maven工程并导入坐标:也就是传统项目的导入jar包,需要再pom.xml中配置mybatis和mysql-connction
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
- 创建实体类和dao类的接口
package com.itheima.domain;
import java.io.Serializable;
import java.util.Date;
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", birthday=" + birthday +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
- 创建Mybatis的主配置文件 sqlMapConfig.xml,放置在resources根目录下
目录结构
src
main
java
resources
SqlMapConfig
test
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--mybatis主配置文件-->
<configuration>
<!--配置环境-->
<environments default="mysql">
<!--配置mysql环境-->
<environment id="mysql">
<!--配置事务的类型-->
<transactionManager type="JDBC"></transactionManager>
<!--配置数据源(连接池)-->
<dataSource type="POOLED">
<!--配置链接数据库的四个基本信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/day17"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!--指定映射配置文件,映射配置文件指的是每个dao独立的配置文件-->
<mappers>
<mapper resource="com/itheima/dao/UserDao.xml"></mapper>
</mappers>
</configuration>
- 创建映射配置文件,也就是UserDao.xml,mybatis的映射配置文件位置必须和dao接口的包结构相同,例如UserDao接口在src/main/java/cn/itheima/dao/UserDao.java,那么xml就应该放在src/main/resources/java/cn/itheima/dao/UserDao.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--映射配置文件的mapper标签的namespace属性的取值必须是dao接口的全限定类名-->
<mapper namespace="com.itheima.dao.UserDao">
<!--配置查询所有,有了他就有了可以执行的sql语句,就可以获取preparedStatement-->
<!--id不可以随便写,要写namespace里面的方法名称-->
<!--resultType告诉了mybatis要把查询结果封装成上面对象-->
<select id="findAll" resultType="com.itheima.domain.User">
select * from user;
</select>
</mapper>
如果遵从了第四点中的全部标注,那么我们在开发中就无需写dao的实现类
5.3 mybatis环境搭建的注意事项
- 创建SqlMapConfig.xml和IUserDao.java的时候,名称是为了和我们之前的知识保持一致,在mybatis中,他把持久层的操作接口名称和配置文件也叫做mapper,所以IUserDao和IUserMapper是一样的
- 在IDEA中,创建目录和包是不一样的,创建包的时候我们可以直接创建com.itheima.xxx,IDEA会自动给他分层三级,但是目录不一样,如果我们创建com.itheima.xxx,会创建一级名为com.itheima.xxx的目录,所以为了显示出分层的效果,我们应当创建三次,这一点在我们创建映射配置文件的时候可以凸显出来
- mybatis的映射配置文件位置必须和dao接口的包接口系统相同,如果dao接口在com.itheima.dao下,那么映射配置文件对于的目录结构也必须是com.itheima.dao
- 映射配置文件的mapper标签的namespace属性的取值必须是dao接口的全限定类名
- 映射配置文件的操作配置,id属性的取值必须是dao接口的方法名,所以通过namespace和id就可以唯一定位一个sql操作
- 当我们遵从了3,4,5点之后,我们在开发中就可以无需创建dao接口的实现类,也就是说,写完接口,我们的操作就结束了
6. mybatis入门案例
6.1 使用xml配置文件的方式
- 步骤
- 读取配置文件
- 创建SqlSesionFactory工厂
- 使用工厂生成一个SqlSession对象
- 使用SqlSession创建Dao接口的代理对象
- 使用代理对象执行方法
- 释放资源
- 注意:不要忘记在映射配置文件中告之mybatis要将结果封装为哪个实体类中
- 配置方式resuleType
package com.itheima.test;
public class MybatisTest {
//入门案例
public static void main(String[] args) throws Exception{
//1. 读取SqlMapConfig配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2. 创建SqlSessionFactory工厂(构建者模式:负责完成复杂SqlSessionFactory对象的构建)
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3. 使用工厂生产一个SqlSession对象
SqlSession session = factory.openSession();
//4. 使用SqlSession创建dao接口的代理对象(因为我们没有书写dao接口的实现类,所以为了在不改变源码的基础上进行扩展,所以我们要进行动态代理)
IUserDao userDao = session.getMapper(IUserDao.class);
//5. 使用代理对象执行方法
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}
//6. 释放资源
session.close();
in.close();
}
}
- 读取配置文件的时候,关于路径的问题,只有3,4是开发的时候经常使用的
- 绝对路径:d:/xxx/xxx/xxx,如果代码在别的机器上运行,没有d盘怎么办,就会出错
- 相对路径:src/java/main/xxx,如果是web工程,一部署,src目录就没有了
- 类加载器:只能读取类路径的配置文件
- 使用servletContext对象的getRealPath()
6.2 使用注解的方式
- 和基于mapper配置文件的区别就是
- 我们只需要书写主配置文件SqlMapConfig.xml就可以,无需书写别的mapper配置文件
- 然后再dao接口对应的方法上,加上注解即可
- 首先,修改dao接口
public interface IUserDao {
// 查询所有操作
@Select("select * from user")
List<User> findAll();
}
- 然后修改主配置文件
本来:
<!--指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件-->
<mappers>
<mapper resource="com/itheima/dao/IUserDao.xml"></mapper>
</mappers>
修改后:
<!--如果是使用注解来配置的话,此处应该使用class属性指定被注解的dao接口全限定类名-->
<mappers>
<mapper class="com.hejiale.dao.IUserDao"></mapper>
</mappers>
6.3 一个小问题(代码看一看就可以,无实际意义)
Mybatis再使用代理dao的方式实现增删改查时做什么事呢?其实可以分成两件事情
- 创建代理对象
- 在代理对象中调用selectList
我们如果不使用代理对象的话,也就不用写这行代码
IUserDao userDao = session.getMapper(IUserDao.class);
那么我们就必须手写实现类
public class UserDaoImpl implements IUserDao {
private SqlSessionFactory factory;
public UserDaoImpl(SqlSessionFactory factory){
this.factory = factory;
}
public List<User> findAll(){
//1.使用工厂创建SqlSession对象
SqlSession session = factory.openSession();
//2.使用session执行查询所有方法
List<User> users = session.selectList("com.itheima.dao.IUserDao.findAll");
session.close();
//3.返回查询结果
return users;
}
}
然后测试代码修改为如下
public class MybatisTest {
/**
* 入门案例
* @param args
*/
public static void main(String[] args)throws Exception {
//1.读取配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3.使用工厂创建dao对象
IUserDao userDao = new UserDaoImpl(factory);
//4.使用代理对象执行方法
List<User> users = userDao.findAll();
for(User user : users){
System.out.println(user);
}
//5.释放资源
in.close();
}
}
这种方法是徒劳的,所以一般都是使用代理对象的方式
第二部分:Mybatis的基本使用
1 常规CRUD
步骤
- 先写IUserDao接口,里面的方法表明我们要对数据库表进行哪些操作
- 然后写mapper配置文件,里面写具体对应的sql语言
- 最后写测试类
注意: - 首先,在当前阶段,数据库表名和实体类的属性名是一致的
package com.hejiale.dao;
public interface IUserDao {
/**
* 查询所有用户
* @return
*/
List<User> findAll();
/**
* 根据id查询用户信息
* @param userId
* @return
*/
User findById(Integer userId);
/**
* 根据名称模糊查询用户信息
* @param username
* @return
*/
List<User> findByName(String username);
/**
* 根据queryVo中的条件查询用户
* @param vo
* @return
*/
List<User> findUserByVo(QueryVo vo);
/**
*
* @return
*/
List<User> findUserByCondition();
/**
* 保存用户
*/
void InsertUser(User user);
/**
* 根据id删除用户
* @param id
*/
void deleteUserById(Integer id);
/**
* 更新用户
* @param user
*/
void updateUser(User user);
int findTotal();
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hejiale.dao.IUserDao">
<select id="findAll" resultType="com.hejiale.domain.User">
select * from user;
</select>
<select id="findById" resultType="com.hejiale.domain.User" parameterType="Integer">
select * from user where id=#{id}
</select>
<select id="findByName" resultType="com.hejiale.domain.User" parameterType="String">
select * from user where username like #{username}
</select>
<insert id="InsertUser" parameterType="com.hejiale.domain.User">
<!--
#{username}这里,mybatis会自动解析出user中对应的属性,但是注意一点,花括号中的
属性名称必须和user类的属性名称一致
-->
insert into user(username,address,sex,birthday) values(#{username},#{address},#{sex},#{birthday})
</insert>
<delete id="deleteUserById" parameterType="Integer">
delete from user where id=#{id}
</delete>
<update id="updateUser" parameterType="com.hejiale.domain.User">
update user set username=#{username},sex=#{sex},address=#{address},birthday=#{birthday} where id=#{id}
</update>
<select id="findTotal" resultType="Integer">
select count(*) from user;
</select>
</mapper>
- MybatisTest.java(测试类)
package com.itheima.test;
/*
mybatis CRUD
*/
public class MybatisTest {
private InputStream inputStream;
private SqlSessionFactoryBuilder builder;
private SqlSessionFactory factory;
private SqlSession session;
private IUserDao mapper;
@Before
public void init() throws IOException {
inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
builder = new SqlSessionFactoryBuilder();
factory = builder.build(inputStream);
session = factory.openSession();
mapper = session.getMapper(IUserDao.class);
}
@After
public void destory() throws IOException {
session.commit();
session.close();
inputStream.close();
}
/**
* 查询所有的测试类,只需要创建代理对象然后执行对应方法就可以
* @throws IOException
*/
@Test
public void TestFindAll() throws IOException {
List<User> users = mapper.findAll();
for (User user : users) {
System.out.println(user);
}
}
/**
* 根据id查用户
*/
@Test
public void TestFindById(){
User byId = mapper.findById(42);
System.out.println(byId);
}
/**
* 根据名称进行模糊查询
*/
@Test
public void TestFindByName(){
List<User> users = mapper.findByName("%王%");
for (User user : users) {
System.out.println(user);
}
}
@Test
public void TestInsertUser(){
User user=new User("李连杰","山西省太原市","男",new Date());
mapper.InsertUser(user);
System.out.println("保存成功");
}
@Test
public void TestDeleteUserById(){
mapper.deleteUserById(52);
System.out.println("删除成功");
}
@Test
public void TestUpdateUser(){
User user = mapper.findById(53);
user.setAddress("中国北京");
mapper.updateUser(user);
System.out.println("更新成功");
}
@Test
public void TestFindTotal(){
int total = mapper.findTotal();
System.out.println(total);
}
}
写到这里,需要注意的地方:
- 我们在为了简化我们插入的操作,我们在实体类中添加了一个含参构造方法,这样子我们在进行保存用户测试的时候,可以更方便的构造保存的User对象,但是这时候,如果我们进行查询操作,或者一切在mapper配置文件中,resultType为User对象的所有sql语句,都会无法顺利执行,因为他们不在默认封装User对象,而是采用你刚刚在User中添加的含参构造方法,所以我们在mybatis中,如果在实体类中定义了含参构造方法,就一定要定义一个无参构造方法
2 返回保存用户的id
我们希望保存用户完成之后,可以返回刚才保存的用户的id,因为在正确的保存操作中,我们创建了一个User对象,然后使用InsertUser方法,将其保存进去,但是我们是不知道该user的id的,想要知道,就必须通过查询操作才可以
<insert id="InsertUser" parameterType="com.hejiale.domain.User">
<!--
配置插入操作后,获取插入数据的id,这里就需要用到标签,selectKey
keyProperty:实体类中对应的名称
keyColumn:数据库表中对应的名称
resultType:返回的结果集类型
order:在正文sql语句之前执行还是之后执行
after
before
-->
<selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
select last_insert_id();
</selectKey>
insert into user(username,address,sex,birthday) values(#{username},#{address},#{sex},#{birthday})
</insert>
@Test
public void TestInsertUser(){
User user=new User("何润东","山西省太原市","男",new Date());
System.out.println("保存前: "+user);
mapper.InsertUser(user);
System.out.println("保存后: "+user);
System.out.println("保存成功");
}
输出
保存前: User{id=null, username='何润东', address='山西省太原市', sex='男', birthday=Thu May 20 09:21:28 CST 2021}
保存后: User{id=54, username='何润东', address='山西省太原市', sex='男', birthday=Thu May 20 09:21:28 CST 2021}
保存成功
根据输出,我们可以看出,进行保存操作之后,会将保存user的id返回,并自动封装回user对象里面
3 OGNL表达式
- Object Graphic Nevigation Language:对象图导航语言
他是通过对象的取值方法来获取数据,写写法上,把get给省略了 - 比如:要获取用户的名称
正常写法:user.getUsername()
OGNL写法:user.username - 这就是mybatis中,为什么我们可以直接写username,而不用user.getUsername()的原因
在paramaterType中已经提供了属性所属的类,所以,不需要写对象名user,可以直接写getUsername
但是又因为,mybatis是使用OGNL表达式解析对象字段的值,所以我们又可以直接写username了
4 将查询条件封装成对象
- 上面例子的查询只是简单的查询,但是在实际的开发中,我们的查询往往是一个综合的查询,比如查询用户信息以及用户购买产品的信息,这个时候,我们可以使用包装对象传递输入参数
package com.hejiale.domain;
public class QueryVo {
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
<!--根据queryVo的条件,查询用户-->
<select id="findUserByVo" parameterType="com.hejiale.domain.QueryVo" resultType="com.hejiale.domain.User">
select * from user where username like #{user.username}
</select>
/**
* 测试使用queryVo作为查询条件
*/
@Test
public void TestFindByVo(){
QueryVo vo=new QueryVo();
User user=new User();
user.setUsername("%王%");
vo.setUser(user);
List<User> userByVo = mapper.findUserByVo(vo);
for (User user1 : userByVo) {
System.out.println(user1);
}
}
5 数据库表属性名称和实体类属性名称不一致
- 之前的案例中,我们数据库属性名称和实体类属性名称都是一致的,比如数据库表为id,username,sex,address,birthday,那么对应的实体类的属性名称也是id,username,sex,address,birthday
- 但是我们现在要做出改变了,因为实际开发中,有可能出现不一致的情况,比如实体类的属性名称分别为:userId,userName,userSex,userAddress,userBirthday
- 在这种情况下,如果不进行改动的话,之前定义的对数据库的操作,大部分都无法完成,但是有一个例外,就是查询全部,findAll,他的查询结果和特殊,不是所有属性都找不到,而是除了username都找不到,那为什么username特殊呢,因为mysql在windows下是不区分大小写的,所以在mysql看来,username和userName是一样的,可以匹配的上的,但是userAddress和address就不是单纯的大小写问题了,所以对不上
针对这种现象,又两种解决方式
5.1 sql语句起别名的方式
给Mapper.xml文件里面的sql语句起别名,比如数据库表里是id,而实体类里面是userId
- select id as userId from user;
- 解决效率高
5.2 在mapper配置文件中进行配置
<!--配置 数据库表的列名和实体类的属性名,的对应关系-->
<!--id是唯一标识,可以随便写 type表示的是查询的实体类是哪个-->
<resultMap id="userMap" type="com.itheima.dao.User">
<!--主键字段的对应-->
<!-- property是实体类中的属性名,column是数据库表中的属性名-->
<id property="userId" column="id"></id>
<!--对非主键字段的对应-->
<result property="userName" column="username"></result>
<result property="userSex" column="usersex"></result>
<result property="userBirthday" column="birthday"></result>
</resultMap>
<!--注意,之后的语句,就不要写resultType了,而要改成写resultMap,属性里面写上对应的resultMap中,设置的id的值-->
<select id="findAll" resultType="userId">
select * from user;
</select>
执行效率低,但是开发效率高,推荐这个
6 SqlMapConfig的其他标签
6.1 properties标签
通过配置properties,我们就可以引用外部配置文件,可以在properties标签内部配置连接数据库信息,也可以通过属性引用外部配置文件信息
- 在标签内部配置的方式
<properties>
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/day17"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</properties>
- 引用外部文件的方式
注意,我们是通过resource属性去指明外部文件的地址的,而不是url,这里详细说明一下url是什么
- URL:Uniform Resource Locater统一资源定位符,它可以唯一的标识一个资源的位置
- 写法:可以分为四部分
- 协议:HTTP,file协议(就是我们windows文件资源管理器里面最常见的路径,例如file:///C:/Users/57/Documents/学习笔记/20_mybatis/mybatis/mybatis_day02/代码/day02_eesy_01mybatisCRUD/src/main/resources/SqlMapConfig.xml,只不过,windows的文件资源管理器会把前面的file省略掉,而且端口也省略了,因为使用了默认端口)
- 主机:localhost
- 端口:8080
- URI:mybatisserver/demo01Servlet
那么上面的例子就可以连起来,写成:
http://localhost:8080/mybatisserver/demo01Servlet
- 上面的例子中提到一个和URL很像的东西:URI(统一资源标识符),他是可以在应用中唯一定位一个资源,比如说我们项目的绝对路径,就是从mybatisserver写起的,那么mybatisserver/demo01Servlet只可以在应用当中定位,但是http://localhost:8080/mybatisserver/demo01Servlet可以在整个网络中唯一的定位
<properties resource="jdbcConfig.properties"></properties>
外部文件jdbcConfig.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/day17
jdbc.username=root
jdbc.password=root
主配置文件引用外部文件内容
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</dataSource>
主配置文件全貌
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="jdbcConfig.properties">
<!-- <property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/day17"/>
<property name="username" value="root"/>
<property name="password" value="root"/> -->
</properties>
<typeAliases>
<package name="com.itheima.domain"></package>
</typeAliases>
<!--配置环境-->
<environments default="mysql">
<!-- 配置mysql的环境-->
<environment id="mysql">
<!-- 配置事务 -->
<transactionManager type="JDBC"></transactionManager>
<!--配置连接池-->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</dataSource>
</environment>
</environments>
<!-- 配置映射文件的位置 -->
<mappers>
<package name="com.hejiale.dao"></package>
</mappers>
</configuration>
6.2 typeAliases标签
- 该标签的主要作用是起别名,那么举个例子,在我们的mapper配置文件当中
<delete id="deleteUserById" parameterType="int">
delete from user where id=#{id}
</delete>
- 上面代码中的parameterType属性的值可以是int,也可以是INT甚至是InT,但是如果parameterType的值是com.hejiale.domain.User,我们就必须保证完全正确,不可以出现大小写错误,这是为什么呢,因为mybatis默认的定义了一些别名,是的即使我们写的是INT,它也可以识别出是int,但是mybatis无法给我们的自定义对象去自动的加上别名,所以,我们可以利用typeAliases来配置自定义别名
- 起别名有两种
- 单个别名定义
- 批量别名定义
<typeAliases>
<!--单个别名定义-->
<!--以后就不用写com.itheima.domain.User了.可以直接写user,而且user不区分大小写,user,UEer都一样-->
<typeAlias alias="user" type="com.itheima.domain.User"/>
<!--批量别名定义,扫描整个包下的类,别名为类名(首字母大写或小写都可以)-->
<!--比如com.itheima.domain有user,有emp,很多domain类,那么以后该包下的类不用写全限定类名,可以直接写类名,比如com.itheima.domain.user变成user,而且同样不区分大小写,即使是UsEr也是可以识别到的-->
<package name="com.itheima.domain"/>
<package name="其它包"/>
</typeAliases>
6.3 mapper标签下的package
- package标签用于指定dao接口所在的包,当指定了之后,就不需要再写mapper子标签去定位mapper配置文件了
<mappers>
<!--<mapper resource="com.hejiale.dao.IUserDao"></mapper>-->
<package name="com.hejiale.dao"></package>
</mappers>
第三部分:mybatis的深入和多表
1 mybatis中的连接池以及事务控制(了解)
- 我们在实际开发中,都需要使用连接池,他可以减少我们获取连接所消耗的时间
- 所谓连接池,其实就是一个存储连接的容器,作为容器,他有以下特点:
- 容器就是一个集合对象,必须是线程安全的
- 必须实现队列的特性,先进先出
- mybatis的连接池
- 提供了三种方式的配置
- 配置的位置
- 主配置文件SqlMapConfig.xml中的dataSource标签,type属性就是表示使用何种连接池方式
- TYPE取值:是三种解决连接池的思想
- POOLED:采用传统的javax.sql.DataSource规范中的连接池,mybatis中有针对规范的实现
- UNPOOLED:采用传统的获取连接的方式,虽然也实现了javax.sql.DataSource接口,但是并没有使用池的思想
- JNDI(扩展):采用服务器提供的技术JNDI实现,来获取dataSource对象,不同的服务器所能拿到的DataSource是不一样的
- 注意:如果不是web或者maven的war工程,是不可以使用的,我们一般使用的是tomcat服务其, 采用的连接池就是dbcp连接池
- TYPE取值:是三种解决连接池的思想
- 主配置文件SqlMapConfig.xml中的dataSource标签,type属性就是表示使用何种连接池方式
- 配置的位置
- 提供了三种方式的配置
1.1 mybatis中连接池使用及分析
1.2 mybatis事务控制的分析
- 事务
- 什么是事务:一个事务可以是一条SQL语句,一组SQL语句或整个程序
- 事务的四大特性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
- 原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
- 一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
- 隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
- 持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
- 不考虑隔离性会产生的3个问题:
- 脏读:脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。
- 不可重复读:一个事务两次读取同一行的数据,结果得到不同状态的结果,中间正好另一个事务更新了该数据,两次结果相异,不可被信任。通俗来讲就是:事务T1在读取某一数据,而事务T2立马修改了这个数据并且提交事务给数据库,事务T1再次读取该数据就得到了不同的结果,发送了不可重复读。
- 幻读(虚读):一个事务执行两次查询,第二次结果集包含第一次中没有或某些行已经被删除的数据,造成两次结果不一致,只是另一个事务在这两次查询中间插入或删除了数据造成的。通俗来讲就是:例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读.
- 解决办法:
- Read Uncommited(读取未提交内容)读未提交,顾名思义,就是一个事务可以读取另一个未提交事务的数据。但是,读未提交产生了脏读,采用读提交可以解决脏读问题
- Read Commited(读取提交内容)读提交,顾名思义,就是一个事务要等另一个事务提交后才能读取数据。读提交,若有事务对数据进行更新(UPDATE)操作时,读操作事务要等待这个更新操作事务提交后才能读取数据,可以解决脏读问题。但在这个事例中,出现了一个事务范围内两个相同的查询却返回了不同数据,这就是不可重复读。但是,读提交两次查询会产生不同的查询结果,就会造成不可重复读问题,采用重复读可以解决此问题。
- Repeatable Read(重复读)重复读,就是在开始读取数据(事务开启)时,不再允许修改操作。重复读可以解决不可重复读问题。应该明白的一点就是,不可重复读对应的是修改,即UPDATE操作。但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作。采用Serializable可以解决幻读问题
- Serializable(可串行化)Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。
- mybatis是通过sqlsession对象的commit和rollback方法实现事务的提交和回滚
2 mybatis基于xml配置的动态sql语句使用(应用)
其实就是mapper配置文件中的几个标签
2.1 if
dao接口
List<User> findUserByCondition(User user);
mapper配置文件
<select id="findUserByCondition" resultMap="userMap" parameterType="user">
select * from user where 1=1
<if test="userName!=null">
and username=#{userName}
</if>
<if test="userAddress!=null">
and address=#{userAddress}
</if>
<if test="userSex!=null">
and sex=#{userSex}
</if>
</select>
在上面的配置文件中需要注意:
- where 1=1 是为了可以成功的拼接,如果我们写的是select * from user where 然后跟着if标签的话,会拼接失败,如果if标签都判断false,那么就不会再sql语句后面拼接任何内容,sql语句就变成select * from user where ;是错误的
- 当实体类属性名称和数据库表列名称不一致的时候,在书写配置文件的时候要注意,只要不是属于sql语句格式的,都按java来写,也就是说要按实体类属性名称写,比如说username=#{userName},花括号的内容是不属于sql语句的,所以按userName写,同理,if标签的判断内容也是写sql语句不涉及的,所以要写成userName
2.2 where
其实就是省的我们写where 1=1 这种语句了,让mybatis帮我们拼接sql语句
<select id="findUserByCondition" resultMap="userMap" parameterType="user">
select * from user
<where>
<if test="userName!=null">
and username=#{userName}
</if>
<if test="userAddress!=null">
and address=#{userAddress}
</if>
<if test="userSex!=null">
and sex=#{userSex}
</if>
</where>
</select>
2.3 foreach
/**
* 满足以下sql查询的方法
* select * from user where id in (41,42,47)
* 换言之
* 根据QueryVo中提供的id集合,查询用户详细
* @param vo
* @return
*/
List<User> findUserInIds(QueryVo vo);
<select id="findUserInIds" resultMap="userMap" parameterType="queryvo">
select * from user
<where>
<if test="ids!=null and ids.size()>0">
<foreach collection="ids" open=" id in (" close=")" item="uid" separator=",">
#{uid}
</foreach>
</if>
</where>
</select>
需要注意的地方就是
<foreach collection="ids" open=" id in (" close=")" item="uid" separator=",">
#{uid}
</foreach>
item的uid和#{uid}要一致
3 mybatis与jdbc的比较
- 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
- 解决:在SqlMapConfig.xml中配置数据链接池,使用连接池管理数据库链接。
- Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
- 解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。
- 向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数对应。
- 解决:Mybatis自动将java对象映射至sql语句,通过statement中的parameterType定义输入参数的类型。
- 对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。
- 解决:Mybatis自动将sql执行结果映射至java对象,通过statement中的resultType定义输出结果的类型。
4 mybatis中的多表操作(掌握)
4.1 一对一
- 示例
- 一个用户可以有多个账户
- 一个账户只可以属于一个用户(多个账户也可以属于同一个用户)
- 步骤
- 建立两张表:用户表,账户表,让用户表和账户表之间具备一对多的关系,需要在账户表中添加外键
- 建立两个实体类:用户实体类和账户实体类,实体类之间也需要体现出一对多的关系,表现一对多关系的方式就是在对应多的实体类当中,添加对应一的实体类作为成员变量
- 建立两个配置文件
- 用户配置文件
- 账户配置文件
- 实现配置
- 当我们查询用户时,可以得到该用户包含的所有账户的信息
- 当我们查询账户时,可以得到该账户所属的用户信息
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL auto_increment,
`username` varchar(32) NOT NULL COMMENT '用户名称',
`birthday` datetime default NULL COMMENT '生日',
`sex` char(1) default NULL COMMENT '性别',
`address` varchar(256) default NULL COMMENT '地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into `user`(`id`,`username`,`birthday`,`sex`,`address`) values (41,'老王','2018-02-27 17:47:08','男','北京'),(42,'小二王','2018-03-02 15:09:37','女','北京金燕龙'),(43,'小二王','2018-03-04 11:34:34','女','北京金燕龙'),(45,'传智播客','2018-03-04 12:04:06','男','北京金燕龙'),(46,'老王','2018-03-07 17:37:26','男','北京'),(48,'小马宝莉','2018-03-08 11:44:00','女','北京修正');
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`ID` int(11) NOT NULL COMMENT '编号',
`UID` int(11) default NULL COMMENT '用户编号',
`MONEY` double default NULL COMMENT '金额',
PRIMARY KEY (`ID`),
KEY `FK_Reference_8` (`UID`),
CONSTRAINT `FK_Reference_8` FOREIGN KEY (`UID`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into `account`(`ID`,`UID`,`MONEY`) values (1,46,1000),(2,45,1000),(3,46,2000);
- 实现一对一,主要有两种方式
- 是在配置文件里面,建立起两个实体类关系的方式
- 通过写account子类的方式
4.1.1配置文件建立联系的方式
- 需求:查询账户信息的同时,查询出所属用户的信息,其实这是一个一对一查询,因为一个账户肯定所属于一个对象
user实体类
package com.hejiale.domain;
import java.io.Serializable;
import java.util.Date;
public class User implements Serializable {
private Integer id;
private String username;
private String address;
private String sex;
private Date birthday;
account实体类
package com.hejiale.domain;
import java.io.Serializable;
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
private User user;
}
IAccountDao.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hejiale.dao.IAccountDao">
<resultMap id="accountUserMap" type="account">
<!--
注意,id标签的column属性应该是aid而不是id,虽然在表中,他的列名的确是id,但是
我们在查询的时候,起了别名是aid,所以这里应该填写的是,我们实际查询出来的表的
列名.
-->
<id property="id" column="aid"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<!--
写到这里,我们可以将account对象的数据封装好,但是account中还包含user,这个对象
我们如何封装呢?
我们要建立一个一对一的关系映射:配置封装user的内容
这里需要使用association标签,内部有两个属性
1. property:就是实体类的属性名称
2. column:你在查询的时候,用account的哪个字段查出user,就填对应的列名,
通俗易懂的说,就是要填写对应的外键列名
3. javaType:告诉mybatis,应该将数据封装成为哪个对象,这里本来应该写成
com.hejiale.domain.User,但是由于在主配置文件里面已经起了别名,所以这里
只需要写user就可以了
-->
<association property="user" column="uid" javaType="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="address" column="address"></result>
<result property="sex" column="sex"></result>
<result property="birthday" column="birthday"></result>
</association>
</resultMap>
<!--查询账户信息,以及账户所属的用户信息-->
<select id="findAll" resultMap="accountUserMap">
SELECT
u.*,a.id as aid,a.uid,a.money
from
account a,user u
WHERE
a.uid=u.id
-- 查询账户信息,以及账户所属的用户信息
</select>
<select id="findById" resultType="account" parameterType="Integer">
select * from account where id=#{id}
</select>
</mapper>
4.1.2写account子类的方式
- 需求:查询所有账户信息的同时,查询出账户所属用户的用户名和地址信息
account的子类accountUser如下
package com.hejiale.domain;
public class AccountUser extends Account{
private String username;
private String address;
// 注意这里,我们调用父类的toString方法,就可以输出account的信息
@Override
public String toString() {
return super.toString()+" AccountUser{" +
"username='" + username + '\'' +
", address='" + address + '\'' +
'}';
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
IAccount.xml
<!--
查询所有账户同时包含用户名和地址信息
-->
<select id="findAllAccount" resultType="AccountUser">
select
a.* , u.username,u.address
from
user u,account a
where
a.uid=u.id
</select>
4.2 一对多
- 需求:查询用户信息以及对应的账户的信息,注意的是:有可能有用户还没有注册账户,所以没有对应的用户信息,所以这个时候,为了保证我们可以查询出所有的用户信息,我们应该使用左外连接
- sql语句如下
-- 查询用户信息以及所关联的账户信息,注意,即使用户没有账户信息,也需要将用户信息打印出来,所以用左外连接比较适合
SELECT
u.*,a.id as aid,a.UID,a.money
from
user u
LEFT JOIN
account a
on
u.id=a.uid
- 因为是一对多,所以实体类当中如果要表示一对多的关系,应该在用户表里,添加一个账户列表的属性
private Integer id;
private String username;
private String address;
private String sex;
private Date birthday;
private List<Account> accounts;
- xml配置如下
<!--
定义user的resultMap
-->
<resultMap id="userAccountMap" type="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="address" column="address"></result>
<result property="sex" column="sex"></result>
<result property="birthday" column="birthday"></result>
<!--
配置user对象中,accounts集合的映射
需要用到collection标签
其中包含两个属性
1. property:实体类对应的名字
2. ofType:因为是集合,所以这里填的是集合内包含的是什么,这里,应该写
account
-->
<collection property="accounts" ofType="account">
<id property="id" column="aid"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
</collection>
</resultMap>
<select id="findAll" resultMap="userAccountMap">
-- 查询用户信息以及所关联的账户信息,注意,即使用户没有账户信息,也需要将用户信息打印出来,所以用左外连接比较适合
SELECT
u.*,a.id as aid,a.UID,a.money
from
user u
LEFT JOIN
account a
on
u.id=a.uid
</select>
4.4 多对多
- 实例:用户和角色
- 通俗易懂的说,我们在家是儿子,在学校是学生,所以我们一个人,可以有多个角色
- 另一方面,又不是说全世界只有你一个人是学生,可以有很多人是学生,所以,一个角色,会赋予多个人
- 那用户和角色就是多对多的关系,但是,如果仔细分析的话,其实,一个多对多关系,可以看成两个一对多关系
- 步骤
- 建立两张表,用户表和角色表,在数据库中,如果要让两个表具有多对多的关系,我们应该建立一个中间表,中间表中的属性就是两个表的主码
- 建立两个实体类,用户类和角色类,那么如果要让两个类具有多对多的关系,我们应该让两个类各自包含对方的集合引用
- 建立两个配置文件,用户的和角色的
- 实现功能:
- 查询用户时候,可以得到用户所包含的角色信息
- 查询角色时候,可以得到角色所属的用户信息
-- 查询角色的时候,还要查询出对应的用户信息
select
r.ID as rid ,r.ROLE_NAME,R.ROLE_DESC,u.*
from
role r
LEFT outer JOIN
user_role ur
on
r.id=ur.rid
left outer join
user u
on
u.id=ur.uid
-- 查询用户的时候,一并查询出用户所属的角色信息
select
u.*,r.ID as rid ,r.ROLE_NAME,r.ROLE_DESC
from
user u
left outer join
user_role ur
on
u.id=ur.UID
left outer join
role r
ON
r.id=ur.RID
- role实体类
public class Role implements Serializable {
private Integer roleId;
private String roleName;
private String roleDesc;
private List<User> users;
}
- role的mapper配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hejiale.dao.IRoleDao">
<resultMap id="roleMap" type="Role">
<id property="roleId" column="rid"></id>
<result property="roleName" column="role_name"></result>
<result property="roleDesc" column="role_desc"></result>
<collection property="users" ofType="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="address" column="address"></result>
<result property="sex" column="sex"></result>
<result property="birthday" column="birthday"></result>
</collection>
</resultMap>
<select id="findAll" resultMap="roleMap">
-- 查询角色的时候,还要查询出对应的用户信息
select
r.ID as rid ,r.ROLE_NAME,R.ROLE_DESC,u.*
from
role r
LEFT outer JOIN
user_role ur
on
r.id=ur.rid
left outer join
user u
on
u.id=ur.uid
</select>
</mapper>
第四部分 mybatis缓存和注解开发
1 Mybatis延迟加载策略
- 问题:在一对多中,当我们有一个用户,他有100个账户,在查询用户的时候,要不要把关联的账户查出来,查询账户的时候,要不要把关联的用户查出来
- 比如说,我们现在只是要查询所有的用户信息,但是我们依然会查询到所有用户连带的账户信息,这会降低我们的效率,所以我们希望,当我们需要查询到附带的信息的时候,再去查询出来,不需要的时候,只查询用户信息就可以,也就是按需查询
- 但是对于账户信息,我们希望的是,查询账户信息的时候,账户所属的用户信息一并查询出来
1.1 什么是延迟加载
- 所以接下来说延迟加载,我们查询用户信息,是可以进行延迟加载的,所谓延迟加载,就是在真正加载数据的时候,才发起查询,不用的时候不查询,也叫按需加载,也叫懒加载
1.2 什么是立即加载
- 不管用不用,只要一调用方法,马上发起查询
1.3 延迟加载与立即加载适用情况
- 我们通常会有四种表对应关系,使用不同的情况
- 一对多和多对多,适用延迟加载
- 多对一和一对一,适用立即加载
1.4 一对一延迟加载
- 首先,我们需要在主配置文件SqlMapConfig中进行相关的设置,打开延迟加载
<!--
该标签必须在properties标签后,typeAlies标签后
-->
<settings>
<!--
延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置
fetchType 属性来覆盖该项的开关状态。
-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--
开启时,任一方法的调用都会加载该对象的所有延迟加载属性,也就是不延迟加载。 否则,
每个延迟加载属性会按需加载(参考lazyLoadTriggerMethods)。
-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
- 之后再mapper配置文件(IAccountDao.xml)中进行设置
<resultMap id="accountUserMap" type="account">
<!--
注意,id标签的column属性应该是aid而不是id,虽然在表中,他的列名的确是id,但是
我们在查询的时候,起了别名是aid,所以这里应该填写的是,我们实际查询出来的表的
列名.
-->
<id property="id" column="id"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<!--
这里我们想实现延迟加载,首先我们许多用到一个新标签
select:
1. select属性指定的内容,是查询用户的唯一标识,其实就是指定一个sql语句
在需要查询用户信息的时候,调用这个sql语句,查询用户信息
2. 我们在IUserDao.xml文件中写了一个findById的sql,可以根据id查询用户
信息
3. 所以,在这里的select属性值应该是:com.hejiale.dao.IUserDao.findById
还需要注意这里的column属性
1. 这里的column属性,应该是我们findById需要用到的列名,也就是uid,findById
用uid查询用户信息
-->
<association property="user" column="uid" javaType="user" select="com.hejiale.dao.IUserDao.findById">
</association>
</resultMap>
<select id="findAll" resultMap="accountUserMap">
SELECT
*
from
account
</select>
- 之后再mapper配置文件(IUserDao.xml)中进行设置
<select id="findById" resultType="user" parameterType="Integer">
select * from user where id=#{id}
</select>
1.5 一对多延迟加载
- 首先同样是要先在主配置文件中配置,和一对一一样,此处掠过
- 然后要在mapper配置文件中配置
<resultMap id="userAccountMap" type="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="address" column="address"></result>
<result property="sex" column="sex"></result>
<result property="birthday" column="birthday"></result>
<!--
配置user对象中,accounts集合的映射
需要用到collection标签
其中包含两个属性
1. property:实体类对应的名字
2. ofType:因为是集合,所以这里填的是集合内包含的是什么,这里,应该写
account
-->
<collection property="accounts" ofType="account" select="com.hejiale.dao.IAccountDao.findAccountByUid" column="id">
</collection>
</resultMap>
<select id="findAll" resultMap="userAccountMap">
-- 查询全部用户信息
SELECT
*
from
user
</select>
- IAccountDao.xml文件中
<select id="findAccountByUid" resultType="account" parameterType="Integer">
select * from account where uid=#{uid}
</select>
1.6 延迟加载的好处
- 我们对比延迟和非延迟的输出结果就可以,首先案例是一对多情况下,查询用户信息,需要的时候,再查询用户的账户信息
- 无延迟加载
2021-05-25 10:29:15,247 811 [ main] DEBUG m.hejiale.dao.IUserDao.findAll - ==> Preparing: -- 查询用户信息以及所关联的账户信息,注意,即使用户没有账户信息,也需要将用户信息打印出来,所以用左外连接比较适合 SELECT u.*,a.id as aid,a.UID,a.money from user u LEFT JOIN account a on u.id=a.uid
2021-05-25 10:29:15,361 925 [ main] DEBUG m.hejiale.dao.IUserDao.findAll - ==> Parameters:
2021-05-25 10:29:15,453 1017 [ main] DEBUG m.hejiale.dao.IUserDao.findAll - <== Total: 11
- 延迟加载
2021-05-25 10:27:57,387 884 [ main] DEBUG m.hejiale.dao.IUserDao.findAll - ==> Preparing: -- 查询全部用户信息 SELECT * from user
2021-05-25 10:27:57,516 1013 [ main] DEBUG m.hejiale.dao.IUserDao.findAll - ==> Parameters:
2021-05-25 10:27:57,796 1293 [ main] DEBUG m.hejiale.dao.IUserDao.findAll - <== Total: 10
2021-05-25 10:27:57,799 1296 [ main] DEBUG o.IAccountDao.findAccountByUid - ==> Preparing: select * from account where uid=?
2021-05-25 10:27:57,800 1297 [ main] DEBUG o.IAccountDao.findAccountByUid - ==> Parameters: 41(Integer)
2021-05-25 10:27:57,803 1300 [ main] DEBUG o.IAccountDao.findAccountByUid - <== Total: 2
2 Mybatis中的缓存
- 什么样的数据可以使用缓存
- 经常查询且不经常改变的
- 数据的正确与否对结果影响不大的
- 什么样的数据不可以使用缓存
- 经常改变的数据
- 数据的正确与否对结果影响很大的,例如:商品库存,银行汇率,股市的价格
Mybatis分为一级缓存和二级缓存
2.1 Mybatis一级缓存
- 指的是Mybatis中SqlSession对象的缓存,当我们执行查询之后,查询的结果,会同时存入到SqlSession中的一个区域中,该区域的结构是一个Map,当我们再次查询同样的数据,mybatis会先去SqlSeesion中查看是否有,如果有,就直接取出,当SqlSession对象消失,Mybatis一级缓存也就消失了
- 一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除,commit(),close()等方法时候,会自动清空缓存
2.2 Mybatis二级缓存
- 他指的是Mybatis中SqlSessionFactory对象的缓存,由同一个SqlSessionFactory创建出的SqlSession对象公用一个二级缓存
- 二级缓存是需要手动打开的,并且要注意的是,一级缓存中存放的是对象,而二级缓存中存放的是数据,而不是对象
- 当我们在使用二级缓存时,所缓存的类一定要实现 java.io.Serializable 接口,这种就可以使用序列化
方式来保存对象 - 使用步骤
- 让Mybatis支持二级缓存(在SqlMapConfig.xml中设置)
- 让当前的映射文件支持二级缓存(IUserDao.xml中配置)
- 让当前的操作支持二级缓存(在select标签中配置)
- SqlMapConfig.xml
<settings>
<!-- 开启二级缓存的支持 -->
<setting name="cacheEnabled" value="true"/>
</settings>
- IUserDao.xml
<!-- <cache>标签表示当前这个 mapper 映射将使用二级缓存,区分的标准就看 mapper 的 namespace 值。 -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.dao.IUserDao">
<!-- 开启二级缓存的支持 -->
<cache></cache>
</mapper>
<!-- 根据 id 查询 -->
<select id="findById" resultType="user" parameterType="int" useCache="true">
select * from user where id = #{uid}
</select>
<!-- 将 UserDao.xml 映射文件中的<select>标签中设置 useCache=”true”代表当前这个 statement 要使用
二级缓存,如果不使用二级缓存可以设置为 false。
注意:针对每次查询都需要最新的数据 sql,要设置成 useCache=false,禁用二级缓存 -->
3 Mybatis注解开发
3.1 注意事项
- 首先,要明白注解开发是省去了哪些步骤,之前的开发,我们都有两部分xml文件,一个是主配置文件,一个是映射配置文件,所以我们注解开发,主要注解的就是映射配置文件的内容,使用了注解开发,我们就可以不再书写映射配置文件了,但是主配置文件依然存在
- 其次注意一点:在Mybatis中,要么使用注解开发,要么使用xml开发,不可以两个都用,否则会报错
3.2 环境搭建
3.3 单表CRUD
package com.hejiale.dao;
import com.hejiale.domain.User;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
/**
* 在mybatis中,针对CRUD,一共有四个注解
* @Select @Insert @Update @Delete
*/
public interface IUserDao {
@Select("select * from user")
List<User> findAll();
/**
* 保存用户
*/
@Insert("insert into user(username,address,sex,birthday) " +
"values(#{username},#{address},#{sex},#{birthday})")
void saveUser(User user);
/**
* 更新用户
*/
@Update("update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id}")
public void updateUser(User user);
/**
* 删除用户
*/
@Delete("delete from user where id=#{id}")
public void deleteUser(Integer id);
/**
* 根据id查询用户
*/
@Select("select * from user where id=#{id}")
public User findById(Integer id);
/**
* 模糊查询
*/
@Select("select * from user where username like #{username}")
public List<User> findByName(String username);
/**
* 聚合函数
*/
@Select("select count(*) from user")
public int findTotal();
}
3.4 实体类属性名称与数据表列名称不一致的情况
/**
* 这里用到的三个属性
* id : 指定resultMap的名称,让别的方法可以调用该resultMap
* value:
* id: 表明是否为主键,boolean类型,默认为false
* column : 数据表列名称
* property : 实体类属性名称
*/
@Select("select * from user")
@Results(
id = "userMap",
value = {
@Result(id = true,column = "id",property = "userId"),
@Result(column = "username",property = "userName"),
@Result(column = "address",property = "userAddress"),
@Result(column = "sex",property = "userSex"),
@Result(column = "birthday",property = "userBirthday")
}
)
List<User> findAll();
/**
* @Result 可以指定用哪个resultMap,并且可以指定多个,当只指定一个的时候,可以省略value和大括号
*/
@ResultMap(value = {
"userMap"
})
@Select("select * from user where id=#{id}")
3.5 多表查询操作
3.5.1 一对一(多对一)
/**
* 查询每个账户,并且获得账户所属的用户信息
* @return
*/
@Select("select * from account")
@Results(
id = "accountMap",
value = {
@Result(id = true,column = "id",property = "id"),
@Result(column = "uid",property = "uid"),
@Result(column = "money",property = "money"),
@Result(property = "user",column = "uid",one=@One(
select="com.hejiale.dao.IUserDao.findById",fetchType= FetchType.EAGER
))
}
)
List<Account> findAll();
3.5.2 一对多
// 查询用户信息,并且查询出用户包含的账户信息
@Select("select * from user")
@Results(
id = "userMap",
value = {
@Result(id = true,column = "id",property = "userId"),
@Result(column = "username",property = "userName"),
@Result(column = "address",property = "userAddress"),
@Result(column = "sex",property = "userSex"),
@Result(column = "birthday",property = "userBirthday"),
@Result(column = "id",property = "accounts",many = @Many(
select = "com.hejiale.dao.IAccountDao.findByUid",fetchType = FetchType.LAZY
))
}
)
List<User> findAll();