spring的依赖注入

如果是经常变化的数据,并不适用于注入的方式

能注入的数据有三类

  • 基本类型和String
  • 其他bean类型(在配置文件或者注解配置过的bean)
  • 复杂类型/集合类型

注入方式

  • 使用构造函数提供
  • 使用set方法提供
  • 使用注解提供

构造函数注入

就是使用类中的构造函数,给成员变量赋值。注意,赋值的操作不是我们自己做的,而是通过配置的方式,让 spring 框架来为我们注入。

优势:在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功

缺点:改变了bean对象的实例化方式,使我们创建对象时,如果用不到这些数据,也必须提供。

要求:类中需要提供一个对应参数列表的构造函数。

标签:constructor-arg,它的属性有:

  • index:指定参数在构造函数参数列表的索引位置,索引位置从0开始

  • type:指定参数在构造函数中的数据类型,该数据类型也是构造函数中某个或某些参数的类型

  • name:指定参数在构造函数中的名称

  • value:它能赋的值是基本数据类型和 String 类型

  • ref:它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class user {
//如果是经常变化的数据,并不适用于注入的方式
private String name;
private Integer age;
private Date birthday;
public user(String name, Integer age, Date birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
}

<!--配置user,构造函数注入-->
<bean id="user" class="cn.hxx.domain.user">
<constructor-arg name="name" value="韩晓璇"/>
<constructor-arg name="age" value="18"/>
<constructor-arg name="birthday" ref="date"/>
</bean>
<!--配置一个日期对象-->
<bean id="date" class="java.util.Date"></bean>

set方法注入

  1. 在类中提供需要注入成员的 set 方法,通过配置文件给 bean 中的属性传值:使用 set 方法的方式

  2. 涉及的标签:property

    name:找的是类中 set 方法后面的部分

    ref:给属性赋值是其他 bean 类型的

    value:给属性赋值是基本数据类型和 string 类型的

  3. 优势:创建对象没有明显的限制,可以直接使用默认构造函数

  4. 缺点:如果某个成员必须有值,则获取对象时可能set方法没有执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class user1 {
//如果是经常变化的数据,并不适用于注入的方式
private String name;
private Integer age;
private Date birthday;
public void setName(String name) {
this.name = name;
}

public void setAge(Integer age) {
this.age = age;
}

public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
<!--set方法注入-->
<bean id="user1" class="cn.hxx.domain.user1">
<property name="age" value="18"></property>
<property name="name" value="韩晓璇"></property>
<property name="birthday" ref="date"></property>
</bean>

复杂方式注入

注入集合数据:list结构的:array、list、set;Map结构的:map、entry、props、prop

在注入集合数据时,只要结构相同,标签可以互换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class user2 {
private String[] myStrs;
private List<String> myList;
private Set mySet;
private Map<String,String> myMap;
private Properties myProps;
//提供setter方法
//....
}
<bean id="user3" class="cn.hxx.domain.user2">
<property name="myStrs">
<array>
<value>AAA</value>
</array>
</property>

<property name="myList">
<list>
<value>AAA</value>
</list>
</property>
<property name="myMap">
<map>
<entry key="testa" value="aaa"></entry>
<entry key="testb">
<value>bbb</value>
</entry>
</map>
</property>
<property name="myProps">
<props>
<prop key="ccc">ccc</prop>
</props>
</property>
</bean>

spring的IOC和实例化bean的配置

1.控制反转

IOC:Inversion of Control,把创建对象的权利交给框架。包括依赖注入和依赖查找

作用:削减计算机程序的耦合(依赖关系)

2.使用 spring 的 IOC 解决程序耦合

spring依赖配置

1
2
3
4
5
6
7
8
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.6</version>
</dependency>
</dependencies>

创建接口和实现类

1
2
3
4
5
6
public interface AccountService {
//。。。。
}
public class AccountServiceImpl implements AccountService {
//。。。。
}

在类路径下或者类路径下的resources中创建一个任意名字的xml文件,给配置文件导入约束;并让spring管理资源

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--把对象的创建交给spring管理-->
<!-- bean 标签:用于配置让 spring 创建对象,并且存入 ioc 容器之中
id 属性:对象的唯一标识。
class 属性:指定要创建对象的全限定类名
-->
<bean id="accountService" class="cn.hxx.service.impl.AccountServiceImpl"></bean>
</beans>

测试是否配置成功

1
2
3
4
5
6
7
8
9
10
public class client {
public static void main(String[] args) {
/*获取spring的IOC核心容器,并根据id获取对象*/
//获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//根据id获取bean对象
AccountService accountService = ac.getBean("accountService", AccountService.class);
System.out.println(accountService);
}
}

3.Spring 基于 XML 的 IOC 细节

1.spring 中工厂的类结构图

3.2BeanFactory 和 ApplicationContext 的区别

BeanFactory时spring容器中的顶层接口,ApplicationContext 时它的子接口

BeanFactory:在创建核心容器时,创建对象采用的策略是延迟加载的方式,即根据id获取对象的时候才会真正的创建对象,适用于创建多例对象

ApplicationContext :在创建核心容器时,创建对象采用的是立即加载的方式,一读完配置文件马上就创建配置文件中配置的对象,适用于创建单例对象

3.3ApplicationContext 接口的实现类

ClassPathXmlApplicationContext:它是从类的根路径下加载配置文件

FileSystemXmlApplicationContext:它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。

**AnnotationConfigApplicationContext:**当我们使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。

4. IOC 中 bean 标签和管理对象细节

4.1bean标签

作用:配置让spring创建对象

属性:

id:给对象在容器中提供一个唯一标识。用于获取对象。

class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。
scope:指定对象的作用范围。

  • singleton :默认值,单例的.
  • prototype :多例的.

request :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中.

session :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中.

global session :WEB 项目中,应用在集群环境的会话范围(全局会话范围)。如果没有集群环境那globalSession 相当于 session.

init-method:指定类中的初始化方法名称。

destroy-method:指定类中销毁方法名称。

4.2实例化 Bean 的三种方式

第一种方式:使用默认无参构造函数。在默认情况下:它会根据默认无参构造函数来创建类对象。如果 bean 中没有默认无参构造函数,将会创建失败。

1
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"/>

第二种方式:spring 管理实例工厂— 使用实例工厂的方法创建对象

该类可能存在于jar包中,无法修改源码提供默认构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 模拟一个实例工厂,创建业务层实现类
* 此工厂创建对象,必须现有工厂实例对象,再调用方法
*/
public class InstanceFactory {
public static AccountService createAccountService(){
return new AccountServiceImpl();
}
}

<!--
此种方式是:
先把工厂的创建交给 spring 来管理。
然后在使用工厂的 bean 来调用里面的方法
factory-bean 属性:用于指定实例工厂 bean 的 id。
factory-method 属性:用于指定实例工厂中创建对象的方法。
-->
<!--spring 管理静态工厂-使用静态工厂的方法创建对象-->
<bean id="instanceFactory" class="cn.hxx.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="createAccountService" ></bean>

第三种方式:spring 管理静态工厂— 使用静态工厂的方法创建对象

1
2
3
4
5
6
7
8
9
10
11
12
public class staticFactory {
public static AccountService createAccountService(){
return new AccountServiceImpl();
}
}
<!-- 此种方式是:
使用 StaticFactory 类中的静态方法 createAccountService 创建对象,并存入 spring 容器
id 属性:指定 bean 的 id,用于从容器中获取
class 属性:指定静态工厂的全限定类名
factory-method 属性:指定生产对象的静态方法
-->
<bean id="accountService" class="cn.hxx.factory.staticFactory" factory-method="createAccountService"></bean>

4.2 bean的作用范围和生命周期

**单例对象:scope=”singleton”**,一个应用只有一个对象的实例。它的作用范围就是整个应用。

​ 生命周期:

​ 对象出生:当应用加载,创建容器时,对象就被创建了。init-method属性

​ 对象活着:只要容器在,对象一直活着。

​ 对象死亡:当应用卸载,销毁容器时,对象就被销毁了。init-destroy属性

**多例对象:scope=”prototype”**,每次访问对象时,都会重新创建对象实例。

​ 生命周期:

​ 对象出生:当使用对象时,spring框架创建新的对象实例。

​ 对象活着:只要对象在使用中,就一直活着。

​ 对象死亡:当对象长时间不用,且没有别的对象引用时,会被 java 的垃圾回收器回收。

1
<bean id="accountService" class="cn.hxx.service.impl.AccountServiceImpl" scope="prototype" init-method="init" destroy-method="destroy"></bean>

MyBatis注解开发

1.mybatis 的常用注解说明

**@Insert:**实现新增

@Update:实现更新

@Delete:实现删除

@Select:实现查询

@Result:实现结果集封装

@Results:可以与@Result 一起使用,封装多个结果集

@ResultMap:实现引用@Results 定义的封装

@One:实现一对一结果集封装

@Many:实现一对多结果集封装

@SelectProvider: 实现动态 SQL 映射

@CacheNamespace:实现注解二级缓存的使用

2.单表CRUD

user表:id、username、birthday、sex、address

user类

1
2
3
4
5
6
7
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
}

userDao接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public interface UserDao {
/*查询所有用户*/
@Select("select * from user")
List<User> findAll();

@Select("select * from user where id = #{id}")
User findById(Integer id);

@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}")
void updateUser(User user);

@Delete("delete from user where id = #{id}")
void deleteById(Integer id);

@Select("select * from user where username like #{name}")
List<User>findUserByName(String name);

@Select("select count(*) from user")
int findTotal();
}

3.复杂关系映射的注解说明

  1. @Results 注解:代替的是标签resultMap

​ 该注解中可以使用单个@Result 注解,也可以使用@Result 集合

​ @Results({@Result(),@Result()})或@Results(@Result())

  1. @Resutl 注解:代替了id标签和result标签

    @Result 中 属性介绍:

    ​ id 是否是主键字段

    ​ column:sql查询数据库后指定的的结果列名

    ​ property 实体类的属性名

    ​ one 需要使用的@One 注解(@Result(one=@One)()))

    ​ many 需要使用的@Many 注解(@Result(many=@many)()))

  2. @One 注解(一对一):代替了assocation标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。

    @One 注解属性介绍:

    ​ select 指定用来多表查询的 sqlmapper:全限定类名.方法名

    ​ fetchType :指定加载方式 lazy(延迟加载)、DEFAULT、EAGER(立即加载)

    使用格式:

    ​ @Result(column=” “,property=””,one=@One(select=””))

  3. @Many 注解(多对一):代替了Collection标签,是多表查询的关键,在注解中用来指定查询返回的对象集合。

    注意:聚集元素用来处理“一对多”的关系。需要指定映射的 Java 实体类的属性,属性的 javaType(一般为 ArrayList)但是注解中可以不定义;

    使用格式:@Result(property=””,column=””,many=@Many(select=””))

3.1列名和sql查询结果的列名不一致的情况下,使用resultMap进行关联

user表:id、username、birthday、sex、address

user1类

1
2
3
4
5
6
7
public class user1 {
private Integer userId;
private String userName;
private Date userBirthday;
private String userSex;
private String userAddress;
}

userDao接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*查询所有用户*/
@Select("select * from user")
//id是本结果集的唯一标识,其他接口方法根据此id引用结果集
//value是Result注解数组
@Results(id = "userMap",value = {
//id标识是否为主键,column标识sql查询后的结果列名,property标识实体类对应的属性名
@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<user1> findAll1();

@Select("select * from user where id = #{id}")
@ResultMap("userMap")//指定结果集,写定义好的Results的id
user1 findById1(Integer id);

3.2一对一关联及延迟加载

需求:加载账户信息时并且加载该账户的用户信息,根据情况可实现延迟加载。(注解方式实现)

添加 User 实体类及 Account 实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
}
public class Account {
private Integer id;
private Integer uid;
private Double money;
/*多对一:一个账户只能属于一个用户,从表方应该包含一个主表方的对象引用*/
private User user;
}

添加账户accountDao的持久层接口并使用注解配置

1
2
3
4
5
6
7
8
9
10
11
public interface AccountDao {
/*查询所有用户*/
@Select("select * from account")
@Results(id = "AccountMap",value = {
@Result(id = true,column = "id",proper ty = "id"),
@Result(column = "uid",property = "uid"),
@Result(column = "money",property = "money"),
@Result(column = "uid",property = "user",one = @One(select = "cn.hxx.Dao.UserDao.findById",fetchType = FetchType.EAGER))
})
List<Account> findAll();
}

添加用户userDao的的持久层接口并使用注解配置

1
2
3
4
public interface UserDao {
@Select("select * from user where id = #{id}")
User findById(Integer id);
}

3.3使用注解实现一对多复杂关系映射

添加 User1 实体类及 Account 实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
public class user1 {
private Integer userId;
private String userName;
private Date userBirthday;
private String userSex;
private String userAddress;
private List<Account> accounts;
}
public class Account1 {
private Integer id;
private Integer uid;
private Double money;
}

添加账户account1Dao的持久层接口并使用注解配置

1
2
3
4
public interface Account1Dao {
@Select("select * from account where uid = #{uid}")
List<Account1> findByUid(Integer id);
}

添加用户user1Dao的的持久层接口并使用注解配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface User1Dao {
/*查询所有用户,并加载出每个用户的所有账户信息*/
@Select("select * from user")
@Results(id = "userAccountsMap",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"),
//查询用户所拥有的账户时,需要根据当前用户的id作为AccountDao.findByUid的参数来查询
@Result(column = "id",property = "accounts",many = @Many(select = "cn.hxx.Dao.Account1Dao.findByUid",fetchType = FetchType.LAZY))
})
List<user1> findAllUserAccounts();
}

4.缓存注解配置

一级缓存默认开启不需要配置,二级缓存需要配置

1.开启全局配置

1
2
3
4
5
<!-- 开启延迟加载的支持 -->
<settings>
<!--全局性地开启或关闭所有映射器配置文件中已配置的任何缓存-->
<setting name="cacheEnabled" value="true"></setting>
</settings>

2.使用注解**@CacheNamespace**

1
2
3
4
@CacheNamespace(blocking = true)
public interface User1Dao {
.....
}

MyBatis缓存

1. 概念

什么是缓存:存在于内存中的临时数据

为什么是缓存:减少和数据库的交互次数,提高执行效率

适用于缓存:经常查询并且不经常改变的;数据的正确与否对最终结果影响不大的

不适用缓存:经常改变的数据;数据的正确与否对最终结果影响大:商品的库存、银行的汇率、股市的牌价

2.MyBatis一级缓存

​ 指的是Mybatis中SqlSession对象的缓存。当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供的一块区域中。该区域的结构是一个Map。当我们再次查询同样的数据,Mybatis会先去SqlSession查询中有,如果有则直接拿来用。当SqlSession对象消失时,一级缓存也消失

一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。这样做的目的为了让缓存中存储的是最新的信息,避免脏读。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void test15(){
List<queryUserRole> Userrole = mapper.findUserRole();
System.out.println(Userrole);/*要打印地址,不要重写toString*/
List<queryUserRole> Userrole1 = mapper.findUserRole();//不发起查询,直接在缓存中取出数据
System.out.println(Userrole1);
/*清除sqlSeeion*/
session.close();
session = factory.openSession();
mapper = session.getMapper(UserDao.class);
List<queryUserRole> Userrole2 = mapper.findUserRole();//发起新的查询
System.out.println(Userrole2);
//清除缓存
session.clearCache();
List<queryUserRole> Userrole3 = mapper.findUserRole();//发起新的查询
System.out.println(Userrole3);
}

测试结果

3.MyBatis二级缓存

​ 指的是MyBatis种SqlSessionFactory对象的缓存。

由同一个SqlSessionFactory对象创建的SqlSession共享其缓存

二级缓存的使用步骤

​ 第一步:让MyBatis框架支持二级缓存(在SqlMapConfig.xml中配置)

1
2
3
4
<settings>
<!--全局性地开启或关闭所有映射器配置文件中已配置的任何缓存,默认为true-->
<setting name="cacheEnabled" value="true"></setting>
</settings>

​ 第二步:让当前的映射文件支持二级缓存(在UserDao.xml中配置), 要启用全局的二级缓存,添加一行:

​ < cache>标签表示当前这个 mapper 映射将使用二级缓存,区分的标准就看 mapper 的 namespace 值。

1
2
3
<mapper namespace="cn.hxx.Dao.AccountDao">
<cache/>
</mapper>

​ 第三步:让当前的操作支持二级缓存(在select标签中配置)

1
2
3
4
<!--将 AccountDao.xml 映射文件中的<select>标签中设置 useCache=”true”代表当前这个 statement 要使用二级缓存,如果不使用二级缓存可以设置为 false。-->
<select id="findById" resultType="account" useCache="true">
select * from account where uid=#{id};
</select>

测试:首先开启 mybatis 的二级缓存。

​ session 去查询用户信息,查询到用户信息会将查询数据存储到二级缓存中。

​ session1 去查询用户信息,首先会去二级缓存中找是否存在数据,如果存在直接从缓存中取出数据

​ session1 删除一个用户,执行 commit 提交,将会清空该 mapper 映射下的二级缓存区域的数据

​ session1 再去查询用户信息,则需要执行sql查询数据库,并存储到二级缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void test1(){
List<Account> list = accountDao.findAll();//第一次查询,执行sql,存入二级缓存
System.out.println(list);
session.close();//关闭session,二级缓存中数据仍然在
SqlSession session1 = factory.openSession();
AccountDao session1Mapper = session1.getMapper(AccountDao.class);
List<Account> list1 = session1Mapper.findAll();//不需要执行sql,在二级缓存中可以取出
System.out.println(list1);//地址发生变化,因为在二级缓存中存储的是数据,不是地址
//删除一个用户
session1Mapper.delete(1);//执行删除,所以清空二级缓存
session1Mapper.findAll();//执行sql,并放入二级缓存
}

测试结果

MyBatis延迟加载

1.概念

问题:在一对多中,当我们有一个用户,它有100个账户。

​ 在查询用户时,用户下的账户信息应该是什么时候使用和查询的

​ 在查询账户时,账户的所属信息应该是随着账户查询时一起查询出来

什么是延迟加载

在真正使用数据时才发起查询,不用的时候不查询。按需加载(懒加载)

什么是立即加载

不管用不用,只要一调用方法,马上发起查询

在对应的四种表关系中:一对多,多对一,一对一,多对多

​ 一对多,多对多:通常情况下都采用延迟加载

​ 多对一,一对一:通常情况下采用立即加载

2.使用 assocation 实现延迟加载

需求:查询账户信息同时查询用户信息。

  1. 在 Mybatis 的配置文件 SqlMapConfig.xml 文件中添加延迟加载的配置。
1
2
3
4
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
  1. 账号的持久层AccountDao接口和持久层映射文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
List<Account> findAll();

<mapper namespace="cn.hxx.Dao.AccountDao">
<!--定义封装account和user的resultMap-->
<resultMap id="accountUserMap" type="account">
<id property="id" column="id"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<association property="user" javaType="User" select="cn.hxx.Dao.UserDao.findById" column="uid">
<!--
select: 填写我们要调用的 select 映射的 id ,根据用户id查询用户信息,调用的是UserDao中的findById方法
column : 填写我们要传递给 select 映射的参数,传递给findById的方法是 sql查询出来的uid
-->
</association>
</resultMap>
<select id="findAll" resultType="account">
select * from account;
</select>
</mapper>
  1. 用户UserDao的持久层接口和配置文件
1
2
3
4
5
User findById(Integer id);

<select id="findById" resultType="cn.hxx.domain.User" parameterType="Integer">
select * from user where id=#{uid};
</select>
  1. 测试
1
2
3
4
@Test
public void test1(){
List<Account> list = accountDao.findAll();
}
  1. 测试结果:因为本次只是将 Account对象查询出来放入 List 集合中,并没有涉及到 User对象,所以就没有

    ​ 发出 SQL 语句查询账户所关联的 User 对象的查询

3.使用 Collection 实现延迟加载

在一对多关系配置的< collection >结点中配置延迟加载策略。< collection >结点中也有 select 属性,column 属性。

需求:完成加载用户对象时,查询该用户所拥有的账户信息。

  1. 用户类中添加账户列表
1
2
3
4
5
6
7
8
9
public class User1 {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
//一对多关系映射,主表实体应该包含从表实体的集合引用
private List<Account1> accounts;
}
  1. 编写用户和账户持久层接口的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public interface UserDao {
//查询所有用户
List<User1> findAll1();
}
<!--配置collection实现延迟加载-->
<resultMap id="AllUser1Map" type="user1">
<id column="id" property="id"></id>
<result column="username" property="username"></result>
<result column="sex" property="sex"></result>
<result column="address" property="address"></result>
<result column="birthday" property="birthday"></result>
<collection property="accounts" ofType="account1"
select="cn.hxx.Dao.AccountDao.findByUid"
column="id">
</collection>
</resultMap>
<!--配置查询所有User1,id是UserDao的方法名-->
<select id="findAll1" resultMap="AllUser1Map">
select * from user;
</select>
  1. 账号的持久层AccountDao接口和持久层映射文件
1
2
3
4
5
6
public interface AccountDao {
List<Account> findByUid(Integer uid);
}
<select id="findByUid" resultType="account">
select * from account where uid=#{id};
</select>
  1. 测试
1
2
3
4
5
6
7
8
@Test
public void test14(){
/*测试collection延迟加载*/
List<User1> user1s = mapper.findAll1();
/*for (User1 user1 : user1s) {
System.out.println(user1);
}*/
}

MyBatis多表查询

1. 一对一查询

数据库表

​ 用户表user:id、username、birthday、sex、address

​ 账户表account:id、uid(外键,参考user.id)、money

需求:查询所有账户信息,关联查询下单用户信息。

注意:因为一个账户信息只能供某个用户使用,所以从查询账户信息出发关联查询用户信息为一对一查询。如果从用户信息出发查询用户下的账户信息则为一对多查询,因为一个用户可以有多个账户。

1.1方式一

思想:定义专门的 po 类作为输出类型,其中定义了 sql 查询结果集所有的字段。

1.封装查询结果类:为了能够封装SQL 语句的查询结果,定义 AccountCustomer 类中要包含账户信息同时还要包含用户信息,所以我们要在定义 AccountUser 类时可以继承 Account类。

1
2
3
4
public class AccountUser extends Account implements Serializable {
private String username;
private String address;
}

2.UserDao接口方法:

1
2
/*一对一查询所有账户,同时得到所属用户姓名和地址*/
List<AccountUser> findAllAccountUser();

3.UserDao.xml配置文件

1
2
3
<select id="findAllAccountUser" resultType="AccountUser">
select a.*,u.username,u.address from user u,account a where a.uid = u.id
</select>

1.2方式二(常用方式)

思想:从表实体应该包含一个主表实体的对象引用

通过面向对象的has a的关系可知,一个账户被一个用户所拥有,所以可以在account类中添加一个user类的对象来表示这个账户的所属用户

  1. account类
1
2
3
4
5
6
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
private User user;
}
  1. UserDao.xml配置文件

    resultMap参数说明:

    • id:指定查询列中的唯一标识

    • type:封装的全限定类名

    id 标签:用于指定所在类的主键字段
    result 标签:用于指定非主键字段

    • column 属性:用于指定sql查询后对应的列名

    • property 属性:用于指定实体类属性名称

    association参数说明:

    ​ 1.property:关联查询的信息,本例中指account下的user属性。
    ​ 2.javaType:关联的属性user的所属类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--定义封装account和user的resultMap-->
<resultMap id="accountUserMap" type="account">
<!--这里的column指定为了aid,是因为select语句将a.id换成了aid,所以查询结果列名为aid-->
<id property="id" column="aid"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>

<association property="user" javaType="User">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result column="sex" property="sex"></result>
<result column="address" property="address"></result>
<result column="birthday" property="birthday"></result>
</association>
</resultMap>

<select id="findAllAccountOfUser" resultMap="accountUserMap">
select u.*,a.id as aid,a.money from user u,account a where a.uid = u.id
</select>

2.一对多查询

数据库表

​ 用户表user:id、username、birthday、sex、address

​ 账户表account:id、uid(外键,参考user.id)、money

需求:查询所有用户,同时获取到用户下所有账户的信息

思想:一对多关系映射,主表实体应该包含从表实体的集合引用

  1. User主表类
1
2
3
4
5
6
7
8
9
10
public class User1 {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;

//一对多关系映射,主表实体应该包含从表实体的集合引用
private List<Account1> accounts;
}
  1. UserDao接口方法
1
2
/*一对多查询 查询所有用户,同时获取到用户下所有账户的信息*/
List<User1>findAllAccountsOfUser();
  1. UserDao.xml配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--定义封装的user1类-->
<resultMap id="userAccountsMap" type="user1">
<id column="id" property="id"></id>
<result column="username" property="username"></result>
<result column="sex" property="sex"></result>
<result column="address" property="address"></result>
<result column="birthday" property="birthday"></result>
<!--配置user对象中accounts集合的映射-->
<!--property写user类所包含的列表集合属性,ofType该列表中成员所属的类-->
<collection property="accounts" ofType="Account1">
<id column="aid" property="id"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
</collection>
</resultMap>
<!--该用户可能没有账户,所以要user表左连接account表-->
<select id="findAllAccountsOfUser" resultMap="userAccountsMap">
select u.*,a.id as aid,a.MONEY from user u LEFT JOIN account a on u.id = a.uid;
</select>

3.多对多查询

示例:用户和角色,一个用户可以有多个角色,一个角色可以赋予个多个用户

  1. 建立两张表:用户表、角色表、中间表

    让用户表和角色表具有多对多的关系,需要使用中间表,中间表包含各自的主键,是外键

    (1) 用户表user:id、username、birthday、sex、address

    (2) 角色表role:id、role_name、role_desc(角色描述)

    (3) 中间表user_role:uid、rid

  2. 建立实体类:用户实体类和角色实体类

    体现多对多的关系:各自包含对方一个集合引用,使用Pojo对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class rUser {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
}
public class role {
private Integer roleId;
private String roleName;
private String roleDesc;
}
/*得到的用户信息,包括用户的角色*/
public class queryUserRole {
private rUser user;
private List<role> roles;
}
/*得到角色信息,以及该角色所赋予的用户*/
public class queryRoleUser {
private role role;
private List<rUser> users;
}
  1. UserDao接口方法
1
2
3
4
5
/*查询用户时,可以得到用户所包含的角色信息*/
List<queryUserRole> findUserRole();

/* 查询角色时可以得到角色的所赋予的用户信息*/
List<queryRoleUser> findRoleUser();
  1. 配置文件

    查询用户时,可以得到用户所包含的角色信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--查询用户时,可以得到用户所包含的角色信息-->
<resultMap id="UserRolesMap" type="queryUserRole">
<!--包含一个user和list-->
<id column="id" property="user.id"></id>
<result column="username" property="user.username"></result>
<result column="sex" property="user.sex"></result>
<result column="address" property="user.address"></result>
<result column="birthday" property="user.birthday"></result>
<collection property="roles" ofType="role">
<id property="roleId" column="rid"></id>
<result property="roleName" column="role_name"></result>
<result property="roleDesc" column="role_desc"></result>
</collection>
</resultMap>

<select id="findUserRole" resultMap="UserRolesMap">
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
</select>

​ 查询角色时可以得到角色的所赋予的用户信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--查询角色时,可以得到该角色所赋予的用户信息-->
<resultMap id="RoleUsersMap" type="queryRoleUser">
<!--包含一个role和list-->
<id property="role.roleId" column="rid"></id>
<result property="role.roleName" column="role_name"></result>
<result property="role.roleDesc" column="role_desc"></result>
<collection property="users" ofType="user">
<id column="id" property="id"></id>
<result column="username" property="username"></result>
<result column="sex" property="sex"></result>
<result column="address" property="address"></result>
<result column="birthday" property="birthday"></result>
</collection>
</resultMap>

<select id="findRoleUser" resultMap="RoleUsersMap">
select role.id as rid,role.role_name,role.role_desc,user.* from role
LEFT OUTER JOIN user_role on role.id = user_role.rid
LEFT outer JOIN user on user.id = user_role.uid
</select>

MyBatis映射文件的动态SQL

我们根据实体类的不同取值,使用不同的 SQL 语句来进行查询。比如在 id 如果不为空时可以根据 id 查询,如果 username 不同空时还要加入用户名作为条件。这种情况在我们的多条件组合查询中经常会碰到。

If标签

  • 根据条件查询,比如可以根据姓名查询,可以根据ID查询,也可以结合两个一起查询
  1. 持久层Dao接口
1
List<User> findByCondition(User user);
  1. 持久层 Dao 映射配置

    注意:if标签的 test 属性中写的是对象的属性名,如果是包装类的对象要使用 OGNL 表达式的写法。比如包装类VoUser类中有一个属性user,如果参数是VoUser的话,就要使用user.username表达式。

    关于大小写的问题:如果SQL语句上的内容就不区分大小写,比如select * from user where 1 = 1 、 and username like、and address like 但是涉及到Java中的类的属性就要严格区分大小大写,比如下面的test属性里面的内容:username和address,还有#{username},#{address} 要严格对应User类中的属性如:getUsername对应username

1
2
3
4
5
6
7
8
9
<select id="findByCondition" parameterType="user" resultType="user">
select * from user where 1 = 1
<if test="username!=null and username!=''">
and username like #{username}
</if>
<if test="address!=null">
and address like #{address}
</if>
</select>

3.测试

1
2
3
4
5
6
7
8
9
10
11
/*测试findByCondition*/
@Test
public void test7(){
User user = new User();
//user.setUsername("韩晓璇");
user.setAddress("陕西西安");
List<User> userList = mapper.findByCondition(user);
for (User u : userList) {
System.out.println(u);
}
}

where标签

  • 在多条件查询时,不需要使用where 1 = 1
1
2
3
4
5
6
7
8
9
10
11
<select id="findByCondition" parameterType="user" resultType="user">
select * from user
<where>
<if test="username!=null and username!=''">
and username like #{username}
</if>
<if test="address!=null">
and address like #{address}
</if>
</where>
</select>

foreach标签

  • SQL 语句:select 字段 from user where id in (?)

  • foreach标签用于遍历集合,它的属性:

    ​ collection:代表要遍历的集合元素,注意编写时不要写 #{}

    ​ open:代表语句的开始部分

    ​ close:代表结束部分

    ​ item:代表遍历集合的每个元素,生成的变量名

    ​ sperator:代表分隔符

  • 需求:根据ID集合查询

    持久层Dao接口

1
List<User> findByIds(queryVo vo);

持久层 Dao 映射配置

1
2
3
4
5
6
7
8
9
10
<select id="findByIds" parameterType="queryVo" resultType="user">
select * from user
<where>
<if test="ids!=null and ids.size()>0">
<foreach collection="ids" open="and id in (" close=")" item="uid" separator=",">
#{uid}
</foreach>
</if>
</where>
</select>

测试

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void test8(){
queryVo Vo = new queryVo();
List<Integer> ids = new ArrayList<>();
ids.add(41);
ids.add(42);
Vo.setIds(ids);
List<User> userList = mapper.findByIds(Vo);
for (User u : userList) {
System.out.println(u);
}
}

抽取重复的SQL语句

1
2
3
4
5
<sql id="defaultSql"> select * from user </sql>
<!--配置查询所有,id是UserDao的方法名-->
<select id="findAll" resultType="cn.hxx.domain.User">
<include refid="defaultSql"></include>
</select>

MyBatis事务

MyBatis框架的事务控制方式

Mybatis 框架因为是对 JDBC 的封装,所以 Mybatis 框架的事务控制方式,本身也是用 JDBC 的setAutoCommit()方法来设置事务提交方式的。

  1. setAutoCommit(false)情况下表示没有开启自动提交事务,需要利用sqlSession.commit()手动提交事务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class MyBatisTest {
private InputStream in;
private SqlSession session;
private UserDao mapper;

@Before //用户在测试方法之前执行
public void init() throws IOException {
//1.读取配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);//这是一个接口,不能直接new
//3.使用工厂生产SqlSession对象
session = factory.openSession();
//4.使用SqlSession创建Dao接口的代理对象
mapper = session.getMapper(UserDao.class);
}

@After //用于在测试之后执行
public void destroy() throws IOException {
//提交事务
session.commit();
in.close();
session.close();
}
/*测试聚合函数count*/
@Test
public void test6(){
int total = mapper.findTotal();
System.out.println(total);
}
}

Mybatis 自动提交事务的设置

CUD(增删改) 过程中必须使用 sqlSession.commit()提交事务。主要原因就是在连接池中取出的连接,都会将调用 connection.setAutoCommit(false)方法。这样我们就必须使用 sqlSession.commit()方法,相当于使用了 JDBC 中的 connection.commit()方法实现事务提交。最后底层是调用的JdbcTransaction类中实现的commit和roolback方法。

如果是查询操作,在sqlSession.commit()的底层是不会执行connection.commit操作的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void commit() throws SQLException {
if (this.connection != null && !this.connection.getAutoCommit()) {
if (log.isDebugEnabled()) {
log.debug("Committing JDBC Connection [" + this.connection + "]");
}

this.connection.commit();
}

}

public void rollback() throws SQLException {
if (this.connection != null && !this.connection.getAutoCommit()) {
if (log.isDebugEnabled()) {
log.debug("Rolling back JDBC Connection [" + this.connection + "]");
}

this.connection.rollback();
}

}

我们可以在factory.openSession(true);传入true,则开启了自动提交。观察DefaultSqlSessionFactory类中的openSession方法,可以传入true或者false选择关闭/开启自动提交事务

1
2
3
public SqlSession openSession(boolean autoCommit) {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, autoCommit);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Before  //用户在测试方法之前执行
public void init() throws IOException {
//1.读取配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);//这是一个接口,不能直接new
//3.使用工厂生产SqlSession对象
session = factory.openSession(true);
//4.使用SqlSession创建Dao接口的代理对象
mapper = session.getMapper(UserDao.class);
}

@After //用于在测试之后执行
public void destroy() throws IOException {
in.close();
session.close();
}

MyBatis数据库连接池

连接池就是存储连接的一个容器。容器是一个集合对象,该集合必须是线程安全的,不能两个线程拿到同一个连接。该集合必须实现队列的特性:先进先出。

MyBatis中连接池

  1. mybatis连接池提供了3种方式配置

  2. 配置的位置:主配置文件MapConfig.xml中datasource标签,type属性就是表示采用哪种连接池方式

  3. type属性的取值:PPOLED、UNPOOLED、JNDI

    (1)POOLED:采用传统的javax.sql.DataSource规范中的连接池。

    (2)UNPOOLED:采用传统的获取连接的方式,实现javax.sql.DataSource接口,但是没有使用池的思想

    (3)JNDI:采用服务器提供的JNDI技术实现,来获取DataSource对象,不同的服务器所能拿到的 DataSource是不一样的。如果不是web或者maven的的war工程,是不能使用的,对于 tomcat服务器采用的是dbcp连接池。

  4. 数据源配置

    MyBatis 在初始化时,根据< dataSource>的 type 属性来创建相应类型的的数据源 DataSource,即:

    type=”POOLED”:MyBatis 会创建 PooledDataSource 实例

    type=”UNPOOLED” : MyBatis 会创建 UnpooledDataSource 实例

    type=”JNDI”:MyBatis 会从 JNDI 服务上查找 DataSource 实例,然后返回使用

1
2
3
4
5
6
<!-- 配置数据源(连接池)信息 --> 
<dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
  1. 获取连接的原理性过程

    mybatis使用的第三方连接池中设计了活动池和空闲池两个列表,加起来是连接池能用的最大连接数。

    对应PooledDataSource中文popConnection方法:

    • 当有新的连接请求时,若空闲池还有连接则直接取出来;

    • 如果空闲池没有连接,则查看活动池中的连接是否已经达到最大数量,没有超过最大数量时会创建一个新的连接,如果达到最大数量时,会将活动池中最先进来的连接进行返回。

SqlMapConfig.xml配置文件

1.properties(属性)

在使用 properties 标签配置时,我们可以采用两种方式指定属性配置。

第一种

1
2
3
4
5
6
7
<dataSource type="POOLED">
<!--配置连接数据库的4个基本信息-->
<property name="driver" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT%2B8"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</dataSource>

第二种

  1. 定义db.properties文件,在src/main/resources目录下
1
2
3
4
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT%2B8
jdbc.username=root
jdbc.password=123456
  1. properties标签配置,在configuration标签下
1
2
3
4
5
6
7
8
9
10
11
12
<!-- 配置连接数据库的信息
resource 属性:用于指定 properties 配置文件的位置,要求配置文件必须在类路径下
resource="jdbcConfig.properties"
url 属性:
URL: Uniform Resource Locator 统一资源定位符 写法:协议 主机 端口 URI
http://localhost:8080/mystroe/CategoryServlet
URI:Uniform Resource Identifier 统一资源标识符,它是可以在 web 应用中唯一定位一个资源的路径
/mystroe/CategoryServlet
-->
<properties resource="db.properties"></properties>
<!--<properties url="file:///E:\JAVA\day01_01mybatis\src\main\resources\db.properties"></properties>
-->
  1. dataSource 标签就变成了引用上面的配置
1
2
3
4
5
6
7
<dataSource type="POOLED">
<!--配置连接数据库的4个基本信息-->
<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>

2.typeAliases(类型别名)

使用typeAliases配置别名,只能配置domain中类的别名

1
2
3
4
5
6
7
8
9
<typeAliases>
<!--用于配置别名,type属性指定的是实体类全限定类名,alias属性指定别名
当指定了别名,就不再区分大小写
-->
<typeAlias type="cn.hxx.domain.User" alias="user"></typeAlias>
<!--用于配置别名的包,当指定之后,该包下的实体类都会注册别名,类名就是别名,不区分大小写-->
<!--<package name="cn.hxx.domain"></package>
<package name="其它包"/>-->
</typeAliases>

UserDao.xml中

1
<insert id="insertOneUser" parameterType="user">

3.mappers映射器

**< mapper resource=””>**使用相对于类路径的资源

1
<mapper resource="cn/hxx/Dao/UserDao.xml"></mapper>

**< mapper class=””>**使用 mapper 接口类路径

注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。

1
<mapper class="cn.hxx.Dao.UserDao"></mapper>

**< package name=””>**注册指定包下的所有 mapper 接口

注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。

1
2
3
4
5
<mappers>
<!--指定了package就不需要写这个了
<mapper resource="cn/hxx/Dao/UserDao.xml"></mapper>-->
<package name="cn.hxx.Dao"></package>
</mappers>