1.Spring定义
- 不同的语境中,Spring 所代表的含义是不同的
- Spring包含狭义和广义两个语境
1.广义
- 广义上的 Spring 泛指以 Spring Framework 为核心的 Spring 技术栈
- 经过十多年的发展,Spring 已经不再是一个单纯的应用框架,而是逐渐发展成为一个由多个不同子项目(模块)组成的成熟技术,例如 Spring Framework、Spring MVC、SpringBoot、Spring Cloud、Spring Data、Spring Security 等,其中 Spring Framework 是其他子项目的基础
- 1.Spring Data:Spring 提供的数据访问模块,对 JDBC 和 ORM 提供了很好的支持。通过它,开发人员可以使用一种相对统一的方式,来访问位于不同类型数据库中的数据
- 2.Spring Batch:一款专门针对企业级系统中的日常批处理任务的轻量级框架,能够帮助开发人员方便的开发出健壮、高效的批处理应用程序
- 3.Spring Security:前身为 Acegi,是 Spring 中较成熟的子模块之一。它是一款可以定制化的身份验证和访问控制框架
- 4.Spring Mobile:是对 Spring MVC 的扩展,用来简化移动端 Web 应用的开发
- 5.Spring Boot:是 Spring 团队提供的全新框架,它为 Spring 以及第三方库一些开箱即用的配置,可以简化 Spring 应用的搭建及开发过程
- 6.Spring Cloud:一款基于 Spring Boot 实现的微服务框架。它并不是某一门技术,而是一系列微服务解决方案或框架的有序集合。它将市面上成熟的、经过验证的微服务框架整合起来,并通过 Spring Boot 的思想进行再封装,屏蔽调其中复杂的配置和实现原理,最终为开发人员提供了一套简单易懂、易部署和易维护的分布式系统开发工具包
2.狭义
- 狭义的 Spring 特指 Spring Framework,通常将它称为 Spring 框架
- Spring 框架是一个分层的、面向切面的 Java 应用程序的一站式轻量级解决方案,它是 Spring 技术栈的核心和基础,是为了解决企业级应用开发的复杂性而创建的
- Spring 有两个核心部分: IOC 和 AOP
- IOC:Inverse of Control 的简写,译为“控制反转”。指把创建对象过程交给 Spring 进行管理
- AOP:Aspect Oriented Programming 的简写,译为“面向切面编程”。AOP 用来封装多个类的公共行为,将那些与业务无关,却为业务模块所共同调用的逻辑封装起来,减少系统的重复代码,降低模块间的耦合度。另外,AOP 还解决一些系统层面上的问题,比如日志、事务、权限等
1.Spring4.0.x版本架构图
- 1.Test:简化Spring项目的测试
- 2.Core Container:核心容器,Spring作为工厂的实现
- 3.AOP+Aspects:面向切面编程,是面向对象编程有利的补充,可以非侵入(需要改写原有代码)的为代码添加额外功能
- 4.Instrumentation+Messaging:辅助功能
- 5.Data Access/Integration:提供数据库访问
- 6.Web:Spring对JavaWeb开发的支持
2.Spring5.0.x版本架构图
- Core Container:核心容器,由spring-beans,spring-core,spring-context,spring-expression(Spring Expression Language,SpEL)4个模块组成
- spring-beans 和 spring-core 模块是 Spring 框架的核心模块,包含了控制反转(Inversion of
Control, IOC)和依赖注入(Dependency Injection, DI)。- BeanFactory 接口是 Spring 框架中的核心接口,它是工厂模式的具体实现。BeanFactory 使用控制反转对应用程序的配置和依赖性规范与实际的应用程序代码进行了分离。但BeanFactory 容器实例化后并不会自动实例化 Bean,只有当 Bean被使用时 BeanFactory 容器才会对该 Bean 进行实例化与依赖关系的装配。
- spring-context 模块构架于核心模块之上,他扩展了 BeanFactory,为她添加了 Bean 生命周期
控制、框架事件体系以及资源加载透明化等功能。此外该模块还提供了许多企业级支持,如邮件访问、远程访问、任务调度等,ApplicationContext 是该模块的核心接口,她是 BeanFactory 的超类,与BeanFactory 不同,ApplicationContext 容器实例化后会自动对所有的单实例 Bean 进行实例化与依赖关系的装配,使之处于待用状态- spring-expression 模块是统一表达式语言(EL)的扩展模块,可以查询、管理运行中的对象,同时也方便的可以调用对象方法、操作数组、集合等。它的语法类似于传统 EL,但提供了额外的功能,最出色的要数函数调用和简单字符串的模板函数。这种语言的特性是基于 Spring 产品的需求而设计,他可以非常方便地同 Spring IOC 进行交互
- AOP: spring-aop 是 Spring 的另一个核心模块,是 AOP 主要的实现模块。作为继 OOP 后,对程序员影响最大的编程思想之一,AOP 极大地开拓了人们对于编程的思路。在 Spring 中,他是以 JVM 的动态代理技术为基础,然后设计出了一系列的 AOP 横切实现,比如前置通知、返回通知、异常通知等,同时,Pointcut 接口来匹配切入点,可以使用现有的切入点来设计横切面,也可以扩展相关方法根据需求进行切入
- spring-aspects 模块集成自 AspectJ 框架,主要是为 Spring AOP 提供多种 AOP 实现方法。
- spring-instrument 模块是基于 JAVA SE 中的”java.lang.instrument”进行设计的,应该算是AOP 的一个支援模块,主要作用是在 JVM 启用时,生成一个代理类,程序员通过代理类在运行时修改类的字节,从而改变一个类的功能,实现 AOP 的功能
- Data Access:数据访问集成,由spring-jdbc,spring-tx,spring-orm,spring-jms,spring-oxm5个模块组成
- spring-jdbc 模块是 Spring 提供的 JDBC 抽象框架的主要实现模块,用于简化 Spring JDBC。主要是提供 JDBC 模板方式、关系数据库对象化方式、SimpleJdbc 方式、事务管理来简化 JDBC 编程,主要实现类是 JdbcTemplate、SimpleJdbcTemplate 以及NamedParameterJdbcTemplate
- spring-tx 模块是 Spring JDBC 事务控制实现模块。使用 Spring 框架,它对事务做了很好的封装,通过它的 AOP 配置,可以灵活的配置在任何一层;但是在很多的需求和应用,直接使用 JDBC 事务控制还是有其优势的。其实,事务是以业务逻辑为基础的;一个完整的业务应该对应业务层里的一个方法;如果业务操作失败,则整个事务回滚;所以,事务控制是绝对应该放在业务层的;但是,持久层的设计则应该遵循一个很重要的原则:保证操作的原子性,即持久层里的每个方法都应该是不可以分割的。所以,在使用 Spring JDBC 事务控制时,应该注意其特殊性
- spring-orm 模块是 ORM 框架支持模块,主要集成 Hibernate, Java Persistence API (JPA) 和Java Data Objects (JDO) 用于资源管理、数据访问对象(DAO)的实现和事务策略。
- spring-jms 模块(Java Messaging Service)能够发送和接受信息,自 Spring Framework 4.1
以后,他还提供了对 spring-messaging 模块的支撑- spring-oxm 模块主要提供一个抽象层以支撑 OXM(OXM 是 Object-to-XML-Mapping 的缩写,它是一个 O/M-mapper,将 java 对象映射成 XML 数据,或者将 XML 数据映射成 java 对象),例如:JAXB,Castor, XMLBeans, JiBX 和 XStream 等
- Web:由 spring-web、spring-webmvc、spring-websocket 和 spring-webflux 4 个模块组成.
- spring-web 模块为 Spring 提供了最基础 Web 支持,主要建立于核心容器之上,通过 Servlet 或
者 Listeners 来初始化 IOC 容器,也包含一些与 Web 相关的支持- spring-webmvc 模 块 众 所 周 知 是 一 个 的 Web-Servlet 模 块 , 实 现 了 Spring MVC
(model-view-Controller)的 Web 应用- spring-websocket 模块主要是与 Web 前端的全双工通讯的协议。(资料缺乏,这是个人理解)
- spring-webflux 是一个新的非堵塞函数式 Reactive Web 框架,可以用来建立异步的,非阻塞,事件驱动的服务,并且扩展性非常好
- 报文发送:即 spring-messaging 模块。
spring-messaging 是从 Spring4 开始新加入的一个模块,主要职责是为 Spring 框架集成一些基
础的报文传送应用。- Test:即 spring-test 模块。
spring-test 模块主要为测试提供支持的,毕竟在不需要发布(程序)到你的应用服务器或者连接
到其他企业设施的情况下能够执行一些集成测试或者其他测试对于任何企业都是非常重要的- Spirng 各模块之间的依赖关系
2.导入依赖
3.导入配置文件
- 新建applicationContext.xml文件,一般放在resources根目录下(可放在任意位置)
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 通过bean标签将对象交给Spring工厂管理 id:唯一标识 class:类的全类名 --> <bean id="person" class="domains.Person"> <property name="name" value="李四"></property> <property name="age" value="18"></property> <property name="sex" value="男"></property> <property name="height" value="175"></property> <property name="weight" value="62"></property> <property name="phone" value="13465034256"></property> </bean> </beans>
3.Spring工厂实现原理
- 读取配置文件(applicationContext.xml),获取class属性值的全类名,通过反射,默认调用类的无参构造方案创建对象
- Spring会针对一个类创建几个对象(Spring创建的对象是否是单例)
- 默认情况下,Spring工厂只创建一个对象(单例)
- 可以通过bean标签的scope属性设置
- singleton:默认值,单例,Spring在创建工厂时已经创建出来
- prototype:多例,Spring在创建工厂时不会创建对象,只有在调用getBean方法时,才开始创建对象
如何区分对象的单例或多例
- 在程序运行时只调用该对象的方法,而不去修改它的属性,一般是单例,否则是多例。
- 即如何一个对象无法满足要求,则需要创建多个对象,即多例
<bean id="person" class="domains.Person" scope="prototype">
<property name="name" value="李四"></property>
<property name="age" value="18"></property>
<property name="sex" value="男"></property>
<property name="height" value="175"></property>
<property name="weight" value="62"></property>
<property name="phone" value="13465034256"></property>
</bean>
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Object person1 = context.getBean("person");
Object person2 = context.getBean("person");
System.out.println(person1);
System.out.println(person2);
/**
* domains.Person@210366b4
* domains.Person@eec5a4a
*/
}
1.set注入
操作:在bean标签中,定义property子标签,在property中设置属性的默认值
本质:通过无参构造创建对象,调用属性的set方法进行赋值(所以需要有无参构造方法,和set方法,否则xml文件会爆红)
注意:类中必须要有无参构造,属性必须要有set方法
1.基本类型+String
<bean id="person" class="domains.Person" scope="prototype">
<property name="name"><value>李四</value></property>
<property name="age" value="18"/>
<property name="sex" value="男"></property>
</bean>
2.属性是另一个对象(自定义类型属性)
<bean id="person" class="domains.Person" scope="prototype">
<property name="name"><value>李四</value></property>
<property name="age" value="18"/>
<property name="sex" value="男"></property>
<!-- <property name="cat" ref="animal"></property>-->
<property name="cat">
<ref bean="animal"/>
</property>
</bean>
<bean id="animal" class="domains.Animal">
<property name="name" value="喵喵"/>
</bean>
3.属性是数组,list,set集合
- array,list,set标签可以互换,但是注意set标签会去重,如果属性为set集合也会去重,所以一般见名知意,各自用各自的对应的标签
- 如果数组,list,set集合中的元素是对象的话,也痛上面一样,set会去重,需要注意
<bean id="testArray" class="domains.TestArray">
<property name="ids">
<array>
<value>1value>
<value>2value>
<value>3value>
<value>3value>
array>
property>
bean>
<bean id="testList" class="domains.TestList">
<property name="ids">
<list>
<value>1value>
<value>2value>
<value>3value>
<value>3value>
list>
property>
bean>
<bean id="testSet" class="domains.TestSet">
<property name="ids">
<set>
<value>1value>
<value>2value>
<value>3value>
<value>3value>
set>
property>
bean>
<bean id="testArrayObject" class="domains.TestArrayObject">
<property name="persons">
<array>
<ref bean="person" />
<ref bean="person" />
array>
property>
bean>
<bean id="testListObject" class="domains.TestListObject">
<property name="persons">
<list>
<ref bean="person"/>
<ref bean="person"/>
list>
property>
bean>
<bean id="testSetObject" class="domains.TestSetObject">
<property name="persons">
<set>
<ref bean="person"/>
<ref bean="person"/>
set>
property>
bean>
TestArray{ids=[1, 2, 3, 3]}
TestList{ids=[1, 2, 3, 3]}
TestSet{ids=[1, 2, 3]}
TestArrayObject{persons=[Person{name='李四', age=18, sex='男', weight=null, height=null, phone='null', cat=domains.Animal@610694f1}, Person{name='李四', age=18, sex='男', weight=null, height=null, phone='null', cat=domains.Animal@610694f1}]}
TestListObject{persons=[Person{name='李四', age=18, sex='男', weight=null, height=null, phone='null', cat=domains.Animal@610694f1}, Person{name='李四', age=18, sex='男', weight=null, height=null, phone='null', cat=domains.Animal@610694f1}]}
TestObject{persons=[Person{name='李四', age=18, sex='男', weight=null, height=null, phone='null', cat=domains.Animal@610694f1}]}
4.属性是Map
- Map是键值对,map标签中嵌套entry
- 如果Map中存放的是基本数据类型,可以直接用key和value,如果是引用数据类型,则需要使用key-ref和value-ref属性值
- Properties也是特殊的Map集合,它需要特殊的props和prop搭配使用
- 注意Map中的键如果重复会被覆盖,Properties的输出结果与bean的顺序相反,但是原理相同,也会覆盖键相同的值
<bean id="testMap" class="domains.TestMap">
<property name="ids">
<map>
<entry key="1" value="李四"/>
<entry key="1" value="王五"/>
<entry key="2" value="赵六"/>
map>
property>
<property name="personMap">
<map>
<entry key-ref="person" value-ref="person"/>
<entry key-ref="person" value-ref="person"/>
map>
property>
bean>
<bean id="testProperties" class="domains.TestProperties">
<property name="ids">
<props>
<prop key="1">李四prop>
<prop key="1">王五prop>
<prop key="2">赵六prop>
<prop key="3">骑士prop>
props>
property>
bean>
TestMap{ids={1=王五, 2=赵六}, personMap={Person{name='李四', age=18, sex='男', weight=null, height=null, phone='null', cat=domains.Animal@63753b6d}=Person{name='李四', age=18, sex='男', weight=null, height=null, phone='null', cat=domains.Animal@63753b6d}}}
TestProperties{ids={3=骑士, 2=赵六, 1=王五}}
2.构造注入
- 原理:通过调用类中不同的构造方法,完成对象的创建,对于属性的赋值,在调用有参构造时完成(不通过set方法注入,需要构造方法)
- 操作:在bean标签中,添加constructor-arg子标签,完成不同构造方法的调用
- 注意:可以使用name属性制定参数名称,使用type属性指定类型,使用index指定顺序,不存在参数类型相同,顺序相同的两个方法,因为这是同一个方法
- 如果参数为对象,则使用ref属性制定制定的对象
<bean id="user" class="domains.User">
<constructor-arg name="name" value="李四"/>
<constructor-arg name="age" value="18"/>
<constructor-arg value="男" type="java.lang.String"/>
<constructor-arg ref="animal" index="3"/>
bean>
1.构造注入时赋值为null
- 如果想注入空值需要使用null标签,不能直接使用value=null
<bean id="user" class="domains.User">
<constructor-arg name="name"><null>null>constructor-arg>
<constructor-arg name="age" ><null>null>constructor-arg>
<constructor-arg value="男" type="java.lang.String"/>
<constructor-arg ref="animal" index="3"/>
bean>
2.内部bean
- 当要求一个bean不能被其他类获取,只为另一个bean使用时,写为内部bean,内部bean不需要id,因为它只能跟随外部bean一起获取,不能被其他方式获取
<bean id="person" class="domains.Person" scope="prototype">
<property name="name"><value>李四value>property>
<property name="age" value="18"/>
<property name="sex" value="男">property>
<property name="cat">
<bean class="domains.Animal">
<property name="name" value="喵喵"/>
bean>
property>
bean>
5.FactoryBean
- 可以直接获取创建复杂对象,不需要经过工厂模板的方式
1.使用方式
- 自定义一个类,实现FactoryBean,实现其中的方法
- 配置xml文件,然后通过Spring工厂可以获取该对象
package domains;
import org.springframework.beans.factory.FactoryBean;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Collection;
public class MyConnectionFactoryBean implements FactoryBean {
//获取复杂对象的方式
public Object getObject() throws Exception {
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("url", "username", "password");
return connection;
}
//返回复杂对象的类型
public Class<?> getObjectType() {
return Collection.class;
}
//返回该对象是否是单例对象,复杂对象不能直接在xml文件中用scope属性设置,需要在这里配置
public boolean isSingleton() {
return false;
}
}
<bean id="connection" class="domains.MyConnectionFactoryBean"/>
6.Spring的IOC和DI
- IOC(Inversion of Controller):控制反转,反转控制
- DI(Dependency Injection):依赖注入
7.Spring Bean的生命周期
- 实例化对象可以使用set注入,调用无参构造,此时属性为空,构造注入,调用对应的构造函数,此时属性有值
- 设置对象属性步骤只有在set注入时才有,因为set注入时,属性值为空
- 检查bean是否实现了aware类型的接口
- BeanPostProcessor前置处理为所有bean对象设置一些人为的处理方式(可以人为干预,扩展性),对所有的bean生效
- 红框:为当前的bean对象设置一些人为的处理方式,只对当前的bean对象生效
- BeanPostProcessor后置处理,为所有的bean对象设置一些人为的处理方式
- 第二个红框,当当前对象销毁时,做一些人为的处理
1.红框中的初始化阶段操作
- 在spring工厂创建对象为对象属性赋值之后,进入到对象的初始化阶段,Spring工厂会自动调用Bean的初始化方法,我们可以在初始化方法中定义一些自定义操作,例:对创建的对象属性值进行检查,修改不符合的属性
1.修改对象属性的两种方法
- 实现InitializingBean接口
- 使用xml配置中bean标签的init-method方法
- 一般情况下,两者不会同时出现,如果同时出现了,先执行接口的方法,后执行init-method属性的方法
package domains;
import org.springframework.beans.factory.InitializingBean;
public class Teacher implements InitializingBean {
private String id;
private String name;
private String password;
public void setId(String id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "Teacher{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
public void afterPropertiesSet() throws Exception {
//检验对象属性
if(this.name.equals("lisi")){
this.name = "张三";
}
System.out.println("这是初始化阶段实现接口的操作");
}
public void myInitMethod(){
if(this.name.equals("lisi")){
this.name = "哈哈";
}
System.out.println("这是自定义的初始化方法");
}
}
<bean id="teacher" class="domains.Teacher" init-method="myInitMethod">
<property name="id" value="1"/>
<property name="name" value="lisi"/>
<property name="password" value="123456"/>bean>
这是初始化阶段实现接口的操作
这是自定义的初始化方法
Teacher{id='1', name='张三', password='123456'}
2.红框中的销毁阶段
- 时机:用户使用完对象,关闭工厂时才会执行,需要手动关闭工厂,即context.closed()
- 需要实现DisposableBean接口或者自定义销毁接口然后在xml文件中配置destroy-method方法
package domains;
import org.springframework.beans.factory.DisposableBean;
public class Teacher implements DisposableBean {
private String id;
private String name;
private String password;
public void setId(String id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "Teacher{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
public void destroy() throws Exception {
System.out.println("实现接口的销毁阶段");
}
public void myDestroyMethod(){
System.out.println("自定义的销毁阶段");
}
}
<bean id="teacher" class="domains.Teacher" destroy-method="myDestroyMethod">
<property name="id" value="1"/>
<property name="name" value="lisi"/>
<property name="password" value="123456"/>
bean>
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Teacher teacher = (Teacher) context.getBean("teacher");
System.out.println(teacher);
context.close();
3.BeanPostProcoress
package domains;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
public class Teacher implements InitializingBean , DisposableBean {
private String id;
private String name;
private String password;
public void setId(String id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "Teacher{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
public void afterPropertiesSet() throws Exception {
//检验对象属性
if(this.name.equals("lisi")){
this.name = "张三";
}
System.out.println("这是初始化阶段实现接口的操作");
}
public void myInitMethod(){
if(this.name.equals("lisi")){
this.name = "哈哈";
}
System.out.println("这是自定义的初始化方法");
}
public void destroy() throws Exception {
System.out.println("实现接口的销毁阶段");
}
public void myDestroyMethod(){
System.out.println("自定义的销毁阶段");
}
}
package domains;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanPostProcessor implements BeanPostProcessor {
//在初始化之前执行
//参数含义
//bean:用户要获取的对象
//beanName:对象对应的bean标签的id
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeanPostProcessor前置方法+对象:" + bean + " id:" + beanName);
return null;
}
//初始化之后执行
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeanPostProcessor后置方法+对象:" + bean + " id:" + beanName);
return null;
}
}
<bean id="teacher" class="domains.Teacher" destroy-method="myDestroyMethod" init-method="myInitMethod">
<property name="id" value="1"/>
<property name="name" value="lisi"/>
<property name="password" value="123456"/>
bean>
<bean class="domains.MyBeanPostProcessor"/>
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Teacher teacher = (Teacher) context.getBean("teacher");
System.out.println(teacher);
context.close();
/** 结果
* BeanPostProcessor前置方法+对象:Teacher{id='1', name='lisi', password='123456'} id:teacher
* 这是初始化阶段实现接口的操作
* 这是自定义的初始化方法
* BeanPostProcessor后置方法+对象:Teacher{id='1', name='张三', password='123456'} id:teacher
* Teacher{id='1', name='张三', password='123456'}
* 实现接口的销毁阶段
* 自定义的销毁阶段
*/
8.Spring中的代理
1.代理模式
- 产出者:只完成根本需求
- 使用者:需要使用根本需求+额外服务
- 代理类:只有额外服务
- 代理类完成额外的功能,根本功能还是交给基本类,使用类使用代理类,代理类一般使用proxy
1.静态代理
- 代理类必须跟原始类实现同一个接口或者继承同一个父类
- 代理类中只提供额外功能,核心功能在代理中以调用原始类的原始方式的形式出现
- 调用者在使用方法时,要创建代理类的对象,调用者只和代理类打交道
package service;
public interface PersonService {
void insert();
}
package service.impl;
import service.PersonService;
public class PersonServiceImpl implements PersonService {
public void insert() {
System.out.println("调用了insert方法");
}
}
package service.proxy;
import service.PersonService;
import service.impl.PersonServiceImpl;
public class PersonServiceProxy implements PersonService {
private PersonServiceImpl ps = new PersonServiceImpl();
public void insert() {
System.out.println("设置手动提交事务");
try {
ps.insert();
System.out.println("提交事务");
} catch (Exception e) {
System.out.println("回滚事务");
throw new RuntimeException();
}
}
}
2.动态代理
- 静态代理问题:
- 1.随着代理的增多,代理类也会逐步增加,代理类代理过于冗长,不利于代理的维护
- 2.多个原始类会使用同一个代理类的增强,静态代理中,原始类每有一个方法,代理类就得写一个方法,无形中也会出现代码冗余的情况
- 解决方法:Spring的动态代理
- 动态代理:代理类不需要手动编写了,当你提供原始类,以及增强代码之后,Spring会自动生成代理类
- 好处:提高开发效率,降低了冗余代理
1.开发步骤
- 1.导入依赖
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.2.8.RELEASEversion>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.5version>
dependency>
- 2.编写原始类,交给Spring工厂管理
package service;
public interface UserService {
void selectAll();
}
package service.impl;
import service.UserService;
public class UserServiceImpl implements UserService {
public void selectAll() {
System.out.println("selectAll方法被调用");
}
}
- 3.写增强类,交给Spring工厂管理
package advice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class MyBeforeAdvice implements MethodBeforeAdvice {
//在原始方法之前执行的增强
/**
*
* @param method 被增强的原始方法对象
* @param args 原始方法的参数
* @param target 返回值
* @throws Throwable
*/
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("myBeforeAdvice do ....");
System.out.println(method);
System.out.println(args);
System.out.println(target);
}
}
- 4.配置切入点
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userServiceImpl" class="service.impl.UserServiceImpl"/>
<bean id="myBeforeAdvice" class="advice.MyBeforeAdvice"/>
<aop:config>
<aop:pointcut id="userServiceAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>
<aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="userServiceAdvice" />
aop:config>
beans>
myBeforeAdvice do ....
public abstract void service.UserService.selectAll()
[Ljava.lang.Object;@7d68ef40
service.impl.UserServiceImpl@5b0abc94
selectAll方法被调用
- 注意context中包含aop组件,所以不需要再导入,不过需要导入aspectj,用于实现AOP做切入点表达式、aop相关注解。而且使用aop在xml中需要导入aop的命名空间,否则会出现没有aop:config的声明错误,xmlns:aop=“http://www.springframework.org/schema/aop”,http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
- 且使用getClass获取对象的类对象后发现该类为代理类class com.sun.proxy.$Proxy3,本质和静态代理一样,只不过是由spring帮我们生成
1.通知(advice)
- 通知(功能增强):为目标方法添加额外功能
- 根据通知在原始方法添加的位置:前置通知,后置通知,异常通知,以及环绕通知
1.前置通知
- 通知会在原始方法之前执行,实现类MethodBeforeAdvice
2.后置通知
- 通知会在原始方法之后执行,实现类AfterReturningAdvice
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userServiceImpl = (UserService) context.getBean("userServiceImpl");
userServiceImpl.selectAll();
System.out.println(userServiceImpl.getClass());
/**
myBeforeAdvice do ....
public abstract void service.UserService.selectAll()
[Ljava.lang.Object;@25b485ba
service.impl.UserServiceImpl@2b546384
selectAll方法被调用
MyAfterAdvice do ....
null
public abstract void service.UserService.selectAll()
[Ljava.lang.Object;@25b485ba
service.impl.UserServiceImpl@2b546384
class com.sun.proxy.$Proxy3
*/
<bean id="userServiceImpl" class="service.impl.UserServiceImpl"/>
<bean id="myBeforeAdvice" class="advice.MyBeforeAdvice"/>
<bean id="myAfterAdvice" class="advice.MyAfterAdvice"/>
<aop:config>
<aop:pointcut id="userServiceBeforeAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>
<aop:pointcut id="userServiceAfterAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>
<aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="userServiceBeforeAdvice" />
<aop:advisor advice-ref="myAfterAdvice" pointcut-ref="userServiceAfterAdvice"/>
aop:config>
3.异常通知
- 会在原始方法抛出异常时执行,实现接口ThrowsAdvice
package service.impl;
import service.UserService;
import java.util.Random;
public class UserServiceImpl implements UserService {
public void selectAll() {
Random random = new Random();
int i = random.nextInt();
if (i % 2 == 0) {
throw new RuntimeException("随机数异常");
}
System.out.println("selectAll方法被调用");
}
}
package advice;
import org.springframework.aop.ThrowsAdvice;
import java.lang.reflect.Method;
public class MyThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(Method method, Object[] objects, Object o, Exception e){
System.out.println(method);
System.out.println(objects);
System.out.println(o);
System.out.println(e);
System.out.println("MyThrowsAdvice do ...");
}
}
<bean id="userServiceImpl" class="service.impl.UserServiceImpl"/>
<bean id="myBeforeAdvice" class="advice.MyBeforeAdvice"/>
<bean id="myAfterAdvice" class="advice.MyAfterAdvice"/>
<bean id="myThrowsAdvice" class="advice.MyThrowsAdvice"/>
<aop:config>
<aop:pointcut id="userServiceBeforeAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>
<aop:pointcut id="userServiceAfterAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>
<aop:pointcut id="userServiceThrowsAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>
<aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="userServiceBeforeAdvice" />
<aop:advisor advice-ref="myAfterAdvice" pointcut-ref="userServiceAfterAdvice"/>
<aop:advisor advice-ref="myThrowsAdvice" pointcut-ref="userServiceThrowsAdvice"/>
aop:config>
E:\JDK\bin\java.exe "-javaagent:E:\IDEA\IntelliJ IDEA 2020.2.1\lib\idea_rt.jar=61207:E:\IDEA\IntelliJ IDEA 2020.2.1\bin" -Dfile.encoding=UTF-8 -classpath E:\JDK\jre\lib\charsets.jar;E:\JDK\jre\lib\deploy.jar;E:\JDK\jre\lib\ext\access-bridge-64.jar;E:\JDK\jre\lib\ext\cldrdata.jar;E:\JDK\jre\lib\ext\dnsns.jar;E:\JDK\jre\lib\ext\jaccess.jar;E:\JDK\jre\lib\ext\jfxrt.jar;E:\JDK\jre\lib\ext\localedata.jar;E:\JDK\jre\lib\ext\nashorn.jar;E:\JDK\jre\lib\ext\sunec.jar;E:\JDK\jre\lib\ext\sunjce_provider.jar;E:\JDK\jre\lib\ext\sunmscapi.jar;E:\JDK\jre\lib\ext\sunpkcs11.jar;E:\JDK\jre\lib\ext\zipfs.jar;E:\JDK\jre\lib\javaws.jar;E:\JDK\jre\lib\jce.jar;E:\JDK\jre\lib\jfr.jar;E:\JDK\jre\lib\jfxswt.jar;E:\JDK\jre\lib\jsse.jar;E:\JDK\jre\lib\management-agent.jar;E:\JDK\jre\lib\plugin.jar;E:\JDK\jre\lib\resources.jar;E:\JDK\jre\lib\rt.jar;E:\MySelfProject\Spring\target\classes;C:\Users\32929\.m2\repository\org\projectlombok\lombok\1.16.10\lombok-1.16.10.jar;C:\Users\32929\.m2\repository\org\springframework\spring-context\5.2.8.RELEASE\spring-context-5.2.8.RELEASE.jar;C:\Users\32929\.m2\repository\org\springframework\spring-aop\5.2.8.RELEASE\spring-aop-5.2.8.RELEASE.jar;C:\Users\32929\.m2\repository\org\springframework\spring-beans\5.2.8.RELEASE\spring-beans-5.2.8.RELEASE.jar;C:\Users\32929\.m2\repository\org\springframework\spring-core\5.2.8.RELEASE\spring-core-5.2.8.RELEASE.jar;C:\Users\32929\.m2\repository\org\springframework\spring-jcl\5.2.8.RELEASE\spring-jcl-5.2.8.RELEASE.jar;C:\Users\32929\.m2\repository\org\springframework\spring-expression\5.2.8.RELEASE\spring-expression-5.2.8.RELEASE.jar;C:\Users\32929\.m2\repository\org\aspectj\aspectjweaver\1.9.5\aspectjweaver-1.9.5.jar controller.SpringTest
myBeforeAdvice do ....
public abstract void service.UserService.selectAll()
[Ljava.lang.Object;@12d3a4e9
service.impl.UserServiceImpl@240237d2
public abstract void service.UserService.selectAll()
[Ljava.lang.Object;@12d3a4e9
service.impl.UserServiceImpl@240237d2
java.lang.RuntimeException: 随机数异常
MyThrowsAdvice do ...
Exception in thread "main" java.lang.RuntimeException: 随机数异常
4.环绕增强
- 会在原始方法的前,后以及发生异常时执行,实现接口MethodInterceptor
package advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MyAroundAdvice implements MethodInterceptor {
//invocation :方法调用者,用户调用原始方法的动作
public Object invoke(MethodInvocation invocation) throws Throwable {
Object proceed = null;
try{
//前置
System.out.println("这是环绕增强的前置增强");
//执行原始方法,返回值为原始方法的返回值
proceed = invocation.proceed();
//后置
System.out.println("这是环绕增强的后置增强");
} catch (Throwable throwable) {
System.out.println("这是环绕增强的异常增强");
} finally {
System.out.println("这是环绕增强的最终增强");
}
return proceed;
}
}
<bean id="userServiceImpl" class="service.impl.UserServiceImpl"/>
<bean id="myBeforeAdvice" class="advice.MyBeforeAdvice"/>
<bean id="myAfterAdvice" class="advice.MyAfterAdvice"/>
<bean id="myThrowsAdvice" class="advice.MyThrowsAdvice"/>
<bean id="myAroundAdvice" class="advice.MyAroundAdvice"/>
<aop:config>
<aop:pointcut id="userServiceBeforeAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>
<aop:pointcut id="userServiceAfterAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>
<aop:pointcut id="userServiceThrowsAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>
<aop:pointcut id="userServiceAroundAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>
<aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="userServiceBeforeAdvice" />
<aop:advisor advice-ref="myAfterAdvice" pointcut-ref="userServiceAfterAdvice"/>
<aop:advisor advice-ref="myThrowsAdvice" pointcut-ref="userServiceThrowsAdvice"/>
<aop:advisor advice-ref="myAroundAdvice" pointcut-ref="userServiceAroundAdvice"/>
aop:config>
myBeforeAdvice do ....
public abstract void service.UserService.selectAll()
[Ljava.lang.Object;@25a65b77
service.impl.UserServiceImpl@2ed0fbae
这是环绕增强的前置增强
selectAll方法被调用
这是环绕增强的后置增强
这是环绕增强的最终增强
MyAfterAdvice do ....
null
public abstract void service.UserService.selectAll()
[Ljava.lang.Object;@25a65b77
service.impl.UserServiceImpl@2ed0fbae
class com.sun.proxy.$Proxy3
- 注意:不论是否发生异常,不会报错,因为会被环绕增强捕获然后输出环绕增强的异常
5.切入点表达式(execution)
- 切入点:需要添加额外功能的方法的位置,通过aop:pointcut标签定义
- 切入点表达式一共有4种写法
1.execution表达式
execution(返回值类型 包名.类名.方法名(参数类型))
- 返回值类型:有返回值时需要指定具体类型,java.util.String。没有返回值时用void,任意返回值时使用*号表示
- 包名,类名,方法名可以用*代替,表示任意
- 参数类型需要指定时可以使用具体类型例Java.util.Integer指定,如果一个任意类型参数可以用*,多个任意类型参数可以用多个星号,任意类型任意个数可以用两个点
<aop:pointcut id="userServiceBeforeAdvice" expression="execution(String service.impl.UserServiceImpl.*(..))"/>
2.args表达式
- 根据形参进行匹配
- args中根据参数类型和顺序进行匹配,如果能匹配上的方法都会被增强
<aop:pointcut id="userServiceBeforeAdvice" expression="args(java.lang.String)"/>
3.within表达式
- 根据全类名进行匹配
<aop:pointcut id="userServiceBeforeAdvice" expression="within(service.impl.UserServiceImpl)"/>
4.@annotion表达式
- 根据自定义注解进行匹配
package annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)//限定注解的使用位置,该注解只能用在方法上
@Retention(RetentionPolicy.RUNTIME)//限定注解有效时机,在被注解代理运行时有效
public @interface MyAnnotation {
}
<aop:pointcut id="userServiceBeforeAdvice" expression="@annotation(annotation.MyAnnotation)"/>
public class UserServiceImpl implements UserService {
@MyAnnotation
public void selectAll(String a) {
System.out.println("selectAll方法被调用");
}
}
表达式可以使用逻辑运算符
- 非!
- 和&&
- 或 ||
9.Spring中的事务控制
- 事务控制的方式:编码式,声明式
1.编码式,将事务控制的代理直接写入service中
JDBC
conn.setAutoCommit(false); 开启手动控制事务
conn.commit() 提交事务
conn.rollback() 回滚
Mybatis
//默认手动提交事务
sqlSession.commit();
sqlSession.rollback();
2.声明式事务控制
- 实现方式:借助Spring的AOP,将事务控制的代码定义成通知,通过AOP编辑进入service的每个方法中
1.导入依赖
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.5version>
dependency>
2.定义业务类
- 定义原始的service实现类对象,在实现类中的方法里写核心代码(调用mapper,功能实现的逻辑)
public class UserServiceImpl implements UserService {
private UserMapper userMapper;
public UserMapper getUserMapper() {
return userMapper;
}
public void setUserMapper(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Override
public List<User> selectAll() {
return userMapper.selectAll();
}
}
<bean id="userService" class="com.baizhi.service.impl.UserServiceImpl">
<property name="userMapper" ref="userMapper"/>
bean>
3.定义增强类
<bean id="txAdvice" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
这个类存在于框架中,只需要配置bean,不要手动创建该类
4.配置事务增强通知
<tx:advice id="txMyAdvice" transaction-manager="txAdvice">
<tx:attributes>
<tx:method name="select*" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
tx:attributes>
tx:advice>
5.编织切点
<aop:config>
<aop:pointcut id="servicePointcut" expression="execution(* com.baizhi.service.impl.*.*(..))"/>
<aop:advisor advice-ref="txMyAdvice" pointcut-ref="servicePointcut"/>
aop:config>
3.事务详解
- read-only:表示只读事务(查询时的事务):极大提高查询的效率,只能使用在查询中
- timeout:超时机制,事务提交或者回滚时间超过制定的时长,事务自动提交失败,自动回滚
- 默认时间:属性值设置为-1,跟数据库默认的超时时间一致
- rollback-for和no-rollbacck-for
- rollback-for:当service发生指定的异常时,执行回滚事务
- no-rollback-for:当service发生指定异常时,不回滚
- 如果事务设置为rollback-for,Spring中默认:
- RunTimeException及其子类:执行回滚
- Exception及其子类:不回滚
- 如果日后你想自定义一个异常
- 需要回滚的异常:继承RuntimeException
- 不需要回滚的异常:继承Exception
4.事务的传播机制
- 在企业开发中,会出现一个service方法调用另一个service方法的情况,传播机制定义了,当一个service方法调用另一个service方法时的事务如何处理
- REQUIRED:如果在执行该service之前,已经开启一个事务,那么本service方法就加入该事务,同用同一个事务. 如果在执行该service之前,没有事务开启,那么就开启一个新的事务
- SUPPORTS:如果在执行该service之前,已经开启一个事务么本service方法就加入该事务,同用同一个事务, 如果在执行该service之前,没有事务开启,那么就以无事务状态运行
- REQUIRES_NEW:无论外部是否有事务,都会创建新的事务,挂起旧的事务
5.Spring基于注解的事务开发
<bean id="txAdvice" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
<tx:annotation-driven transaction-manager="txAdvice"/>
@Override
@Transactional(readOnly = true,timeout = 3,propagation = Propagation.REQUIRED)
public List<User> selectAll() {
return userMapper.selectAll();
}
- 传统开发中,注解控制事务使用很少,多数还是使用xml控制事务,但是在SpringBoot中只提供了注解控制事务的方式
10.Spring注解开发
好处:可以简化配置
1.开启Spring注解的包扫描
<context:component-scan base-package="com.baizhi.service,com.baizhi.dao"/>
目前我们的mapper是由mybatis生成的,mapper文件是不需要spring扫描的
如果日后使用的不是mybatis,需要将com.baizhi.dao也被spring扫描
2.为目标类和类的属性添加注解
@Component("abc")
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
public UserMapper getUserMapper() {
return userMapper;
}
public void setUserMapper(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Override
public List<User> selectAll() {
return userMapper.selectAll();
}
}
3.注解开发的优化
- @Component替换bean,将类交给Spring工厂管理
- 将@Component细化为三个标签
- @Controller 用在控制器类
- @Service 用在service类
- @Repository 用在dao类上
- 将注解添加在类上之后,会自动生成id
- 注意:
- 1.controller service repository 作用与Component一样,更具有识别性
- 2.Mybiats会帮我们自动实现dao,dao不需要Spring实现的,目前Repository注解不需要使用的
- 3.注解在使用时可以不添加参数,不用手动设置id值,id值默认为类名首字母小写UserServiceImpl ==> getBean(“userServiceImpl”)
- @Autowired 完成属性值的注入的
- 注意:
- 1.@Autowired 根据属性的类型完成注入的,默认从Spring中找与属性类型一直或为其子类的对象
- 2.使用反射注入,不需要属性提供set方法
- @Quaifier(“bean的id值”)
- 与@Autowired一起使用,当有多个相同类型的对象时,帮助@Autowired选择正确的对象
- @Scope 是否单例
- 在不添加此注解时,默认为单例模式
- 如果希望对象为多例 @Scope(“prototype”)
1.Data Access/Integration(数据访问/集成)
数据访问/集成层包括 JDBC、ORM、OXM、JMS 和 Transactions 模块,具体介绍如下。
- JDBC 模块:提供了一个 JBDC 的样例模板,使用这些模板能消除传统冗长的 JDBC 编码还有必须的事务控制,而且能享受到 Spring 管理事务的好处。
- ORM 模块:提供与流行的“对象-关系”映射框架无缝集成的 API,包括 JPA、JDO、Hibernate 和 MyBatis 等。而且还可以使用 Spring 事务管理,无需额外控制事务。
- OXM 模块:提供了一个支持 Object /XML 映射的抽象层实现,如 JAXB、Castor、XMLBeans、JiBX 和 XStream。将 Java 对象映射成 XML 数据,或者将XML 数据映射成 Java 对象。
- JMS 模块:指 Java 消息服务,提供一套 “消息生产者、消息消费者”模板用于更加简单的使用 JMS,JMS 用于用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。
- Transactions 事务模块:支持编程和声明式事务管理。
2.Web 模块
Spring 的 Web 层包括 Web、Servlet、WebSocket 和 Portlet 组件,具体介绍如下。
- Web 模块:提供了基本的 Web 开发集成特性,例如多文件上传功能、使用的 Servlet 监听器的 IOC 容器初始化以及 Web 应用上下文。
- Servlet 模块:提供了一个 Spring MVC Web 框架实现。Spring MVC 框架提供了基于注解的请求资源注入、更简单的数据绑定、数据验证等及一套非常易用的 JSP 标签,完全无缝与 Spring 其他技术协作。
- WebSocket 模块:提供了简单的接口,用户只要实现响应的接口就可以快速的搭建 WebSocket Server,从而实现双向通讯。
- Portlet 模块:提供了在 Portlet 环境中使用 MVC 实现,类似 Web-Servlet 模块的功能。
3.Core Container(Spring 的核心容器)
Spring 的核心容器是其他模块建立的基础,由 Beans 模块、Core 核心模块、Context 上下文模块和 SpEL 表达式语言模块组成,没有这些核心容器,也不可能有 AOP、Web 等上层的功能。具体介绍如下。
- Beans 模块:提供了框架的基础部分,包括控制反转和依赖注入。
- Core 核心模块:封装了 Spring 框架的底层部分,包括资源访问、类型转换及一些常用工具类。
- Context 上下文模块:建立在 Core 和 Beans 模块的基础之上,集成 Beans 模块功能并添加资源绑定、数据验证、国际化、Java EE 支持、容器生命周期、事件传播等。ApplicationContext 接口是上下文模块的焦点。
- SpEL 模块:提供了强大的表达式语言支持,支持访问和修改属性值,方法调用,支持访问及修改数组、容器和索引器,命名变量,支持算数和逻辑运算,支持从 Spring 容器获取 Bean,它也支持列表投影、选择和一般的列表聚合等。
4. AOP、Aspects、Instrumentation 和 Messaging
在 Core Container 之上是 AOP、Aspects 等模块,具体介绍如下:
- AOP 模块:提供了面向切面编程实现,提供比如日志记录、权限控制、性能统计等通用功能和业务逻辑分离的技术,并且能动态的把这些功能添加到需要的代码中,这样各司其职,降低业务逻辑和通用功能的耦合。
- Aspects 模块:提供与 AspectJ 的集成,是一个功能强大且成熟的面向切面编程(AOP)框架。
- Instrumentation 模块:提供了类工具的支持和类加载器的实现,可以在特定的应用服务器中使用。
- messaging 模块:Spring 4.0 以后新增了消息(Spring-messaging)模块,该模块提供了对消息传递体系结构和协议的支持。
5.Test 模块
- Test 模块:Spring 支持 Junit 和 TestNG 测试框架,而且还额外提供了一些基于 Spring 的测试功能,比如在测试 Web 框架时,模拟 Http 请求的功能。
bean的定义信息:BeanDefinition
- bean对象有两种生成方式
1.xml
2.注解
BeanDefinitionReader
不同的读取方式。该接口的实现类
不同的方式解析方式也不同,都会生成BeanDefinition,即Bean的定义信息
通过工厂和反射将Bean实例化
3.(控制反转)Spring IOC
- 在传统的 Java 应用中,一个类想要调用另一个类中的属性或方法,通常会先在其代码中通过 new Object() 的方式将后者的对象创建出来,然后才能实现属性或方法的调用。为了方便理解和描述,我们可以将前者称为“调用者”,将后者称为“被调用者”。也就是说,调用者掌握着被调用者对象创建的控制权。
- 但在 Spring 应用中,Java 对象创建的控制权是掌握在 IoC 容器手里的,其大致步骤如下。
- 1.开发人员通过 XML 配置文件、注解、Java 配置类等方式,对 Java 对象进行定义,例如在 XML 配置文件中使用 标签、在 Java 类上使用 @Component 注解等。
- 2.Spring 启动时,IoC 容器会自动根据对象定义,将这些对象创建并管理起来。这些被 IoC 容器创建并管理的对象被称为 Spring Bean。
- 3.当我们想要使用某个 Bean 时,可以直接从 IoC 容器中获取(例如通过 ApplicationContext 的 getBean() 方法),而不需要手动通过代码(例如 new Obejct() 的方式)创建。
- IoC 带来的最大改变不是代码层面的,而是从思想层面上发生了“主从换位”的改变。原本调用者是主动的一方,它想要使用什么资源就会主动出击,自己创建;但在 Spring 应用中,IoC 容器掌握着主动权,调用者则变成了被动的一方,被动的等待 IoC 容器创建它所需要的对象(Bean)。
1.依赖注入(DI)
- 依赖注入(Denpendency Injection,简写为 DI)是 Martin Fowler 在 2004 年在对“控制反转”进行解释时提出的。Martin Fowler 认为“控制反转”一词很晦涩,无法让人很直接的理解“到底是哪里反转了”,因此他建议使用“依赖注入”来代替“控制反转”。
- 在面向对象中,对象和对象之间是存在一种叫做“依赖”的关系。简单来说,依赖关系就是在一个对象中需要用到另外一个对象,即对象中存在一个属性,该属性是另外一个类的对象。
- 从代码可以看出,B 中存在一个 A 类型的对象属性 a,此时我们就可以说 B 的对象依赖于对象 a。而依赖注入就是就是基于这种“依赖关系”而产生的。
- 我们知道,控制反转核心思想就是由 Spring 负责对象的创建。在对象创建过程中,Spring 会自动根据依赖关系,将它依赖的对象注入到当前对象中,这就是所谓的“依赖注入”
- 依赖注入本质上是 Spring Bean 属性注入的一种,只不过这个属性是一个对象属性而已。
public class B { String bid; A a; }
2.IoC 的工作原理
- 在 Java 软件开发过程中,系统中的各个对象之间、各个模块之间、软件系统和硬件系统之间,或多或少都存在一定的耦合关系。
- 若一个系统的耦合度过高,那么就会造成难以维护的问题,但完全没有耦合的代码几乎无法完成任何工作,这是由于几乎所有的功能都需要代码之间相互协作、相互依赖才能完成。因此我们在程序设计时,所秉承的思想一般都是在不影响系统功能的前提下,最大限度的降低耦合度。
- IoC 底层通过工厂模式、Java 的反射机制、XML 解析等技术,将代码的耦合度降低到最低限度,其主要步骤如下。
- 1.在配置文件(例如 Bean.xml)中,对各个对象以及它们之间的依赖关系进行配置;
- 2.我们可以把 IoC 容器当做一个工厂,这个工厂的产品就是 Spring Bean;
- 3.容器启动时会加载并解析这些配置文件,得到对象的基本信息以及它们之间的依赖关系;
- 4.IoC 利用 Java 的反射机制,根据类名生成相应的对象(即 Spring Bean),并根据依赖关系将这个对象注入到依赖它的对象中。
- 由于对象的基本信息、对象之间的依赖关系都是在配置文件中定义的,并没有在代码中紧密耦合,因此即使对象发生改变,我们也只需要在配置文件中进行修改即可,而无须对 Java 代码进行修改,这就是 Spring IoC 实现解耦的原理。
3.IoC 容器的两种实现
- IoC 思想基于 IoC 容器实现的,IoC 容器底层其实就是一个 Bean 工厂。Spring 框架为我们提供了两种不同类型 IoC 容器,它们分别是 BeanFactory 和 ApplicationContext。
BeanFactory
- BeanFactory 是 IoC 容器的基本实现,也是 Spring 提供的最简单的 IoC 容器,它提供了 IoC 容器最基本的功能,由 org.springframework.beans.factory.BeanFactory 接口定义。
- BeanFactory 采用懒加载(lazy-load)机制,容器在加载配置文件时并不会立刻创建 Java 对象,只有程序中获取(使用)这个对对象时才会创建。
public static void main(String[] args) { BeanFactory context = new ClassPathXmlApplicationContext("Beans.xml"); HelloWorld obj = context.getBean("helloWorld", HelloWorld.class); obj.getMessage(); }
- 注意:BeanFactory 是 Spring 内部使用接口,通常情况下不提供给开发人员使用。
ApplicationContext
- ApplicationContext 是 BeanFactory 接口的子接口,是对 BeanFactory 的扩展。
- ApplicationContext 接口有两个常用的实现类
- 1.ClassPathXmlApplicationContext:加载类路径 ClassPath 下指定的 XML 配置文件,并完成 ApplicationContext 的实例化工作
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(String configLocation);
- 2.FileSystemXmlApplicationContext:加载指定的文件系统路径中指定的 XML 配置文件,并完成 ApplicationContext 的实例化工作
ApplicationContext applicationContext = new FileSystemXmlApplicationContext(String configLocation);
- 在上表的示例代码中,参数 configLocation 用于指定 Spring 配置文件的名称和位置,如 Beans.xml。
public static void main(String[] args) { //使用 FileSystemXmlApplicationContext 加载指定路径下的配置文件 Bean.xml BeanFactory context = new FileSystemXmlApplicationContext("D:\\eclipe workspace\\spring workspace\\HelloSpring\\src\\Beans.xml"); HelloWorld obj = context.getBean("helloWorld", HelloWorld.class); obj.getMessage(); }
4.Spring Bean定义
- 由 Spring IoC 容器管理的对象称为 Bean,Bean 根据 Spring 配置文件中的信息创建。
- 我们可以把 Spring IoC 容器看作是一个大工厂,Bean 相当于工厂的产品。如果希望这个大工厂生产和管理 Bean,就需要告诉容器需要哪些 Bean,以哪种方式装配。
- Spring 配置文件支持两种格式,即 XML 文件格式和 Properties 文件格式。
- Properties 配置文件主要以 key-value 键值对的形式存在,只能赋值,不能进行其他操作,适用于简单的属性配置。
- XML 配置文件采用树形结构,结构清晰,相较于 Properties 文件更加灵活。但是 XML 配置比较繁琐,适用于大型的复杂的项目。
- 通常情况下,Spring 的配置文件都是使用 XML 格式的。XML 配置文件的根元素是 < beans>,该元素包含了多个子元素 < bean>。每一个 < bean> 元素都定义了一个 Bean,并描述了该 Bean 是如何被装配到 Spring 容器中的
<?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 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean id="helloWorld" class="net.biancheng.c.HelloWorld"> <property name="message" value="Hello World!"/> </bean> </beans>
- 在 XML 配置的 元素中可以包含多个属性或子元素,常用的属性或子元素如下表所示。
- id:Bean 的唯一标识符,Spring IoC 容器对 Bean 的配置和管理都通过该属性完成。id 的值必须以字母开始,可以使用字母、数字、下划线等符号。
- name:该属性表示 Bean 的名称,我们可以通过 name 属性为同一个 Bean 同时指定多个名称,每个名称之间用逗号或分号隔开。Spring 容器可以通过 name 属性配置和管理容器中的 Bean。
- class:该属性指定了 Bean 的具体实现类,它必须是一个完整的类名,即类的全限定名。
- scope:表示 Bean 的作用域,属性值可以为 singleton(单例)、prototype(原型)、request、session 和 global Session。默认值是 singleton。
- constructor-arg: 元素的子元素,我们可以通过该元素,将构造参数传入,以实现 Bean 的实例化。该元素的 index 属性指定构造参数的序号(从 0 开始),type 属性指定构造参数的类型。
- property:元素的子元素,用于调用 Bean 实例中的 setter 方法对属性进行赋值,从而完成属性的注入。该元素的 name 属性用于指定 Bean 实例中相应的属性名。
- ref: 和 等元素的子元索,用于指定对某个 Bean 实例的引用,即 元素中的 id 或 name 属性。
- value: 和 等元素的子元素,用于直接指定一个常量值。
- list:用于封装 List 或数组类型的属性注入。
- set:用于封装 Set 类型的属性注入。
- map:用于封装 Map 类型的属性注入。
- entry: 元素的子元素,用于设置一个键值对。其 key 属性指定字符串类型的键值,ref 或 value 子元素指定其值。
- init-method:容器加载 Bean 时调用该方法,类似于 Servlet 中的 init() 方法
- destroy-method:容器删除 Bean 时调用该方法,类似于 Servlet 中的 destroy() 方法。该方法只在 scope=singleton 时有效
- lazy-init:懒加载,值为 true,容器在首次请求时才会创建 Bean 实例;值为 false,容器在启动时创建 Bean 实例。该方法只在 scope=singleton 时有效
Spring Bean属性注入
- 所谓 Bean 属性注入,简单点说就是将属性注入到 Bean 中的过程,而这属性既可以普通属性,也可以是一个对象(Bean)
- Spring 主要通过以下 2 种方式实现属性注入:
- 构造函数注入
- setter 注入(又称设值注入)
1.构造函数注入
- 我们可以通过 Bean 的带参构造函数,以实现 Bean 的属性注入。
- 使用构造函数实现属性注入大致步骤如下:
- 1.在 Bean 中添加一个有参构造函数,构造函数内的每一个参数代表一个需要注入的属性;
- 2.在 Spring 的 XML 配置文件中,通过 及其子元素 对 Bean 进行定义;
- 3.在 元素内使用 元素,对构造函数内的属性进行赋值,Bean 的构造函数内有多少参数,就需要使用多少个 元素。
1.MVC模式
- Model(模型层):提供程序的运行基础业务模型(domains,dao,service)
- View(视图层):接收服务器的数据并展示到具体页面(html,jsp,vue)
- Controller(控制层):接收浏览器发送的请求并控制程序的流程(controller)
2.MVC框架
- MVC框架是遵循MVC开发模式的web层框架,解决的是Controller层的问题
- 1.如何配置controller的访问路径
- 2.如何在controller中接收请求发送的数据
- 3.如何在controller中实现跳转
- 4.如何在跳转时传输数据
3.传统Servlet的缺点
- 1.编码繁琐,开发效率低(必须继承父类重写service方法且一个servlet类最多提供一个服务方法)
- 2.获取请求数据繁琐(手动获取参数并完成类型转换)
- 3.数据传输编码繁琐(手动将数据保存到对应作用域)
- 4.跳转编码方式不统一(请求转发和重定向有不同的方法)
@WebServlet("/xxx") public class XxxServlet extends HttpServlet{ public void service(xxx req,xxx resp){ //1.收参 String username = req.getParameter("username"); ... //2.调用业务层 ... //3.传递参数 req.setAttribute("名字","值") ... //4.跳转 req.getRequestDispatcher("地址").forward(req,resp)//请求转发 resp.sendRedirect("地址")//重定向 } }
1.新建web项目
- 创建maven项目,且使用maven的webapp骨架
2.导入依赖
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0modelVersion> <groupId>org.examplegroupId> <artifactId>SpringMVCartifactId> <version>1.0-SNAPSHOTversion> <packaging>warpackaging> <name>SpringMVC Maven Webappname> <url>http://www.example.comurl> <properties> <project.build.sourceEncoding>UTF-8project.build.sourceEncoding> <maven.compiler.source>1.8maven.compiler.source> <maven.compiler.target>1.8maven.compiler.target> properties> <dependencies> <dependency> <groupId>org.springframeworkgroupId> <artifactId>spring-webmvcartifactId> <version>5.2.8.RELEASEversion> dependency> dependencies> project>
3.引入配置文件springmvc-servlet.xml文件
- 位置:main/resources文件夹下
- 需要导入mvc和context命名空间,否则无法识别
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <mvc:annotation-driven/> <context:component-scan base-package="controller"/> beans>
4.配置web.xml文件
- web.xml需要配置请求分发器,将请求交给springmvc管理
- pring MVC 是 Spring 提供的一个基于 MVC 设计模式的轻量级 Web 开发框架,本质上相当于 Servlet
- Spring MVC 是基于 Servlet 的,DispatcherServlet 是整个 Spring MVC 框架的核心,主要负责截获请求并将其分派给相应的处理器处理。所以配置 Spring MVC,首先要定义 DispatcherServlet。跟所有 Servlet 一样,用户必须在 web.xml 中进行配置。
DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Applicationdisplay-name> <servlet> <servlet-name>DispatcherServletservlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class> <init-param> <param-name>contextConfigLocationparam-name> <param-value>classpath:springmvc-servlet.xmlparam-value> init-param> <load-on-startup>1load-on-startup> servlet> <servlet-mapping> <servlet-name>DispatcherServletservlet-name> <url-pattern>*.dourl-pattern> servlet-mapping> web-app>
5.安装并配置tomcat
6.编码
package controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; @Controller public class FirstController { @RequestMapping("/hello") public ModelAndView hello(){ //业务 System.out.println("Hello SpringMVC"); //跳转 ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName("redirect:/index.jsp"); //转发:forward:/index.jsp //重定向:redirect:/index.jsp //返回值 return modelAndView; } @RequestMapping("/bey") public ModelAndView bey(){ //业务 System.out.println("bey SpringMVC"); //跳转 ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName("redirect:/index.jsp"); //转发:forward:/index.jsp //重定向:redirect:/index.jsp //返回值 return modelAndView; } }
7.访问
- http://localhost:端口号/项目名/requestMapping路径.do
- 例:http://localhost:8080/SpringMVC_war/hello.do
- 注意重定向会改变地址栏地址,请求转发不会
8.整体架构图
5.SpringMVC相关注解
1.@RequestMapping注解
- 注意类和方法上的/都不会影响访问的路径,可有可无
- 1.value:匹配请求访问的路径。
//请求路径需要包含value的值 @RequestMapping(value = "/hello",method = RequestMethod.POST)
- 2.method:设定处理的请求方式。
//get请求:a标签,地址栏输入,表单get提交方法 //post请求:表单post提交方法 @RequestMapping(value = "/hello",method = RequestMethod.POST)
2.@RequestParam注解
- 1.value:设置参数值的别名,传参时需要使用该别名,否则无法获取
- 2.required:设置该值是否为必须的
- 3.defaultValue:设置当该值没有时的默认值
@RequestMapping(value = "/insert",method = RequestMethod.GET) public ModelAndView insert(String name, Integer age, @RequestParam(value = "AliasHeight", required = true, defaultValue = "72") Double height){ //... }
6.接受请求发送的数据
1.基本数据包装类+String
- 基本数据类型可以直接通过地址栏拼接,通过名称一致的形参自动赋值
- 一般使用基本数据类型包装类,否则如果参数不存在,默认值为null时基本数据类型会报错500
@RequestMapping(value = "/insert",method = RequestMethod.GET) public ModelAndView insert(String name, Integer age, Double height){ //业务 System.out.println("Insert SpringMVC"); System.out.println(name); System.out.println(age); System.out.println(height); //跳转 ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName("redirect:/index.jsp"); //转发:forward:/index.jsp //重定向:redirect:/index.jsp //返回值 return modelAndView; }
http://localhost:8080/SpringMVC_war/test/insert.do?name=李四sir&age=18 Insert SpringMVC ??????sir 18 null
2.对象类型
3.数组,List,Set
4.对象集合
5.Map
- 对应处理器指定请求方式的控制器方法,SpringMVC中提供了@RquestMapping的派生注解
- 处理get请求的映射->@GetMapping
- 处理post请求的映射->@PostMapping
- 处理put请求的映射->@PutMapping
- 处理delete请求的映射->@DeleteMaping
4.@RequestMapping的params属性
- params属性通过请求的请求参数匹配请求映射,不匹配则报400
- params属性是一个字符串类型的数组,可以通过四种表达式设置请求参数和请求映射的匹配关系,如果有多个参数则必须全部满足才能映射成功
- param:要求请求映射所匹配的请求必须携带param请求参数
- !param:要求请求映射所匹配的请求必须不能携带param请求参数
- param=value:要求请求映射所匹配的请求必须携带param请求参数且param=value
- param!=value:要求请求映射所匹配的请求必须携带param请求参数,但是param!=value
5.@RequestMapping的headers属性
- headers属性通过请求的请求头信息匹配请求映射
- headers属性是一个字符串类型的数组,可以通过四种表达式设置请求头信息和请求映射的匹配关系
- header:要求请求映射所匹配的请求必须携带header请求头信息
- !header:要求请求映射所匹配的请求必须不能携带header请求头信息
- header=value:要求请求映射所匹配的请求必须携带header请求头且header=value
- header!=value:要求请求映射所匹配的请求必须携带header请求头,但是header!=value
SpringMVC支持ant风格的路径
- 请求地址中的value设置以下值
- ?:表示任意的单个字符(一些有特殊含义的字符不行,?, / 等)
- :表示任意的0个或多个字符
- **:表示任意的一层或多层目录
- 注意在使用时,只能使用//xxx的方式
SpringMVC支持路径中的占位符(重点)
- 原始方式:/deleteUser?id=1
- rest方式:/deleteUser/1
- SpringMVC路径中的占位符常用于restful风格中,当请求路径中将某些数据通过路径的方式传输到服务器中,就可以在相应的@RequestMapping注解的vallue属性中通过占位符{xxx}表示传输的数据,再通过@PathVariable注解,将占位符所表示的数据赋值给控制器方法的形参
- 如果value路径中有占位符,则请求路径中也必须有值,需要对应
SpringMVC获取请求参数
1.通过servletAPI获取
- 将HttpServletRequset作为控制器方法的形参,此时HttpServletRequest类型的参数表示封装了当前请求的请求报文的对象
2.通过控制器方法的形参获取请求参数
- 在控制器方法的形参位置,设置和请求参数同名的形参,当浏览器发送请求,匹配到请求映射时,在DispatcherServlet中就会将请求参数赋值给相应的形参
- 对于单个的同名的参数直接赋值给形参,对于多个同名的参数可以使用字符串接收,结果中间会以逗号隔开,也可以通过字符串类型的数组接收
- 如果请求参数和形参名称不一致则不能进行赋值
@RequestParam
- 将请求参数和控制器方法的形参创建映射关系
- RequestParam注解一共有三个属性
- value:指定为形参赋值的请求参数的参数名
- required:设置是否必须传输此参数请求参数,默认值为true
- 若设置为true时,则当前请求必须传输value所指定的请求参数,若没有传输该请求参数,且没有设置defaultValue属性,则页面报错400;若设置为false,则当前请求不是必须传输value所指定的请求参数,若没有传输,则注解所标示的形参的值为null;
- defaultValue:不管required属性值为true或false,当value所指定的请求参数没有传输或传输的值为“”时,则使用默认值为形参赋值
@RequestHeader
- @ReqeustHeader是将请求头信息和控制器方法的形参创建映射关系
- @RequestHeader注解一共有三个属性:value,required,defaultValue,用法同@RequestParam
- 想要获取请求头信息则必须使用该参数
@CookieValue
- CookieValue是将cookie数据和控制器方法的形参创建映射关系
- CookieValue注解一共有三个属性:value,required,defaultValue,用法同@RequestParam
通过POJO获取请求参数
- 可以在控制器方法的形参位置设置一个实体类类型的形参,此时若浏览器传输的请求参数的参数名和实体类中的属性名一致,那么请求参数就会为此属性赋值
解决获取请求参数的乱码问题
- 解决获取请求参数的乱码问题,可以使用SpringMVC提供的编码过滤器CharacterEncodingFilter,但是必须在web.xml中进行注册
- SpringMVC中处理编码的过滤器一定要配置到其他过滤器之前,否则无效
域对象共享数据
- 1.使用servletAPI向request域对象共享数据
- 2.使用ModelAndView向request域对象共享数据
- 3.使用Model向request域对象共享数据
- 4.使用map向requsest
- 5.使用ModelMap向request域对象共享数据
- 6.Model,ModelMap,Map的关系
- Model,ModelMap,Map类型的参数其实本质上都是BingingAwareModelMap类型的
7.向session域共享数据
8.向application域共享数据
SpringMVC的视图
- SpringMVC中的视图是View接口,视图的作用渲染数据,将模型Model中的数据展示给用户
- SpringMVC视图的种类很多,默认有转发视图和重定向视图
- 当工厂引入jstl的依赖,默认视图会自动转换为JstlView
- 若使用的视图技术为Thymeleaf,在SpringMVC的配置文件中配置了Thymelead的视图解析器,由此视图解析器解析之后所得到的的是ThymeleafView
ThymeleafView
- 当控制器方法中所设置的视图名称没有任何前缀时,
RESTFul
- REST:Representatation State Transfer,表现层资源状态转移
- 是一种软件架构的风格,一种格式
RESTFul的实现
- 具体说就是HTTP协议里面,四个表示操作方式的动词:GET,POST,PUT,DELETE
- 他们分别对应四种基本操作:GET用来获取资源,POST用来新建资源,PUT用来更新资源,DELETE用来删除资源
- REST风格提倡URL地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不使用问号键值对方式携带请求参数,而是将要发送给服务器的数据作为URL地址的一部分,以保证整体风格的一致性
操作 传统方式 REST风格 查询操作 getUserById?id=1 user/1–>get请求方式 保存操作 saveUser user–>post请求方式 删除操作 deleteUser?id=1 user/1–>delete请求方式 更新操作 updateUser user–>put请求方式
HiddenHttpMethodFilter
- 由于浏览器只支持发送get和post方式的请求,那么该如何发送put和delete请求
- SpringMVC提供了HiddenHttpMethodFilter帮助我们将POST请求转换为DELETE或PUT请求
CharacterEncodingFilter和HiddenHttpMethodFilter
- 设置编码的时候有一个前提条件,在次之前不能获取任何的请求参数,只要获取了,编码就没有效果
- HiddenHttpMethodFilter中获取了_Method参数
- 多个过滤器由filter-mapping的位置决定执行顺序,越靠前越先执行
- 所以需要先设置编码过滤器,在设置参数过滤器
HttpMessageConverter
- HttpMessageConverter,报文信息转换器,将请求报文转换为Java对象,或将Java对象转换为响应报文
- 报文分为请求报文和响应报文,请求报文从浏览器发送到服务器,响应报文是将服务器发送到浏览器
- HttpMessageConverter提供了两个注解和两个类型:@RequestBody,@ResponseBody,RequestEntity,ResponseEntity
@RequestBody
- @RequestBody可以获取请求体,需要在控制器方法设置一个形参,使用@RequestBody进行标识,当前请求的请求体就会为当前注解所标识的形参赋值
- @RequestBody将请求报文中的请求体转换为Java数据
@ResponseBody
- 将Java数据转换为响应体
- 用于标识一个控制器方法,可以将该方法的返回值直接作为响应报文的响应体响应到浏览器
@ResponseBody处理json
- 浏览器不能识别Java对象,需要将浏览器的Java对象转换为json字符串,json是一个数据交互模式
- @ResponseBody处理json的步骤
- 1.导入jsckson的依赖
- 2.在SpringMVC的核心配置文件中开启mvc的注解驱动,此时在HandlerAdaptor中会自动装配一个消息转换器:MappingJackson2HttpMessageConverter,可以将响应到浏览器的Java对象转换为Json格式的字符串
- 在处理器方法上使用@ResponseBody注解进行标识
- 将Java对象直接作为控制器方法的返回值返回,就会自动转换为Json格式的字符串
RequestEntity
- 请求实体,可以接收整个请求报文,既可以获取请求头,也可以获取请求体
- RequestEntity是封装请求报文的一种类型,需要在控制器方法的形参中设置该类型的形参,当前请求的请求报文就会赋值给该形参,可以通过getHeaders()获取请求头信息,通过getBody()获取请求体信息
ResponseEntity
- 类型,可以将Java数据转换为相应报文
JSON
- JSON是一种数据交互模式
- XML最多用来做配置文件
- JSON用来做数据转换
历史
- MyBatis最初是Apache的一个开源项目iBatis,2010年6月这个项目由Apache Software Foundation迁移到了Google Code.随着开发团队转投Google Code旗下,iBatis3.x正式更名为MyBatis,代码于2013年11月迁移到Github
- iBatis一词
1.定义
- MyBatis 是一个开源、轻量级的数据持久化框架,是 JDBC 和 Hibernate 的替代方案。MyBatis 内部封装了 JDBC,简化了加载驱动、创建连接、创建 statement 等繁杂的过程,开发者只需要关注 SQL 语句本身。
- 数据持久化是将内存中的数据模型转换为存储模型,以及将存储模型转换为内存中数据模型的统称。例如,文件的存储、数据的读取以及对数据表的增删改查等都是数据持久化操作
- MyBatis 支持定制化 SQL、存储过程以及高级映射,可以在实体类和 SQL 语句之间建立映射关系,是一种半自动化的 ORM 实现。其封装性低于 Hibernate,但性能优秀、小巧、简单易学、应用广泛。
- ORM(Object Relational Mapping,对象关系映射)是一种数据持久化技术,它在对象模型和关系型数据库之间建立起对应关系,并且提供了一种机制,通过 JavaBean 对象去操作数据库表中的数据。
- MyBatis 前身为 IBatis,2002 年由 Clinton Begin 发布。2010 年从 Apache 迁移到 Google,并改名为 MyBatis,2013 年又迁移到了 Github。
- MyBatis 的主要思想是将程序中的大量 SQL 语句剥离出来,使用 XML 文件或注解的方式实现 SQL 的灵活配置,将 SQL 语句与程序代码分离,在不修改程序代码的情况下,直接在配置文件中修改 SQL 语句。
- MyBatis 与其它持久性框架最大的不同是,MyBatis 强调使用 SQL,而其它框架(例如 Hibernate)通常使用自定义查询语言,即 HQL(Hibernate查询语言)或 EJB QL(Enterprise JavaBeans查询语言)
- 优点
- MyBatis 是免费且开源的。
- 与 JDBC 相比,减少了 50% 以上的代码量。
- MyBatis 是最简单的持久化框架,小巧并且简单易学。
- MyBatis 相当灵活,不会对应用程序或者数据库的现有设计强加任何影响,SQL 写在 XML 中,和程序逻辑代码分离,降低耦合度,便于同一管理和优化,提高了代码的可重用性。
- 提供 XML 标签,支持编写动态 SQL 语句。
- 提供映射标签,支持对象与数据库的 ORM 字段关系映射。
- 支持存储过程。MyBatis 以存储过程的形式封装 SQL,可以将业务逻辑保留在数据库之外,增强应用程序的可移植性、更易于部署和测试。
- 缺点
- 编写 SQL 语句工作量较大,对开发人员编写 SQL 语句的功底有一定要求。
- SQL 语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
- 使用场景
- MyBatis 专注于 SQL 本身,是一个足够灵活的 DAO 层解决方案。适用于性能要求高,且需求变化较多的项目,如互联网项目。
2.MyBatis和Hibernate的区别
- Hibernate 和 MyBatis 都是目前业界中主流的对象关系映射(ORM)框架,它们的主要区别如下。
- 1.sql 优化方面
- Hibernate 使用 HQL(Hibernate Query Language)语句,独立于数据库。不需要编写大量的 SQL,就可以完全映射,但会多消耗性能,且开发人员不能自主的进行 SQL 性能优化。提供了日志、缓存、级联(级联比 MyBatis 强大)等特性。
- MyBatis 需要手动编写 SQL,所以灵活多变。支持动态 SQL、处理列表、动态生成表名、支持存储过程。工作量相对较大。
- 2.开发方面
- MyBatis 是一个半自动映射的框架,因为 MyBatis 需要手动匹配 POJO 和 SQL 的映射关系。
- Hibernate 是一个全表映射的框架,只需提供 POJO 和映射关系即可
- 3.缓存机制比较
- Hibernate 的二级缓存配置在 SessionFactory 生成的配置文件中进行详细配置,然后再在具体的表-对象映射中配置缓存。
- MyBatis 的二级缓存配置在每个具体的表-对象映射中进行详细配置,这样针对不同的表可以自定义不同的缓存机制。并且 Mybatis 可以在命名空间中共享相同的缓存配置和实例,通过 Cache-ref 来实现。
- Hibernate 对查询对象有着良好的管理机制,用户无需关心 SQL。所以在使用二级缓存时如果出现脏数据,系统会报出错误并提示。而 MyBatis 在这一方面,使用二级缓存时需要特别小心。如果不能完全确定数据更新操作的波及范围,避免 Cache 的盲目使用。否则脏数据的出现会给系统的正常运行带来很大的隐患。
- Hibernate 优势
- Hibernate 的 DAO 层开发比 MyBatis 简单,Mybatis 需要维护 SQL 和结果映射。
- Hibernate 对对象的维护和缓存要比 MyBatis 好,对增删改查的对象的维护要方便。
- Hibernate 数据库移植性很好,MyBatis 的数据库移植性不好,不同的数据库需要写不同 SQL。
- Hibernate 有更好的二级缓存机制,可以使用第三方缓存。MyBatis 本身提供的缓存机制不佳。
- Mybatis优势
- MyBatis 可以进行更为细致的 SQL 优化,可以减少查询字段。
- MyBatis 容易掌握,而 Hibernate 门槛较高。
- 应用场景
- MyBatis 适合需求多变的互联网项目,例如电商项目、金融类型、旅游类、售票类项目等。
- Hibernate 适合需求明确、业务固定的项目,例如 OA 项目、ERP 项目和 CRM 项目
- 总结
- 总的来说,MyBatis 是一个小巧、方便、高效、简单、直接、半自动化的持久层框架,Hibernate 是一个强大、方便、高效、复杂、间接、全自动化的持久层框架。
- 对于性能要求不太苛刻的系统,比如管理系统、ERP 等推荐使用 Hibernate,而对于性能要求高、响应快、灵活的系统则推荐使用 MyBatis。
3.第一个MyBatis程序
- 创建 MyBatis 程序的步骤为:下载jar包 -> 部署jar包 -> 编写MyBatis核心配置文件 -> 创建实体类 -> 创建DAO接口 -> 创建SQL映射文件 -> 编写测试类
4.Mybatis核心对象
- MyBatis 有三个基本要素:
- 核心接口和类
- MyBatis核心配置文件(mybatis-config.xml)
- SQL映射文件(mapper.xml)
- 每个 MyBatis 应用程序都以一个 SqlSessionFactory 对象的实例为核心。
- 首先获取 SqlSessionFactoryBuilder 对象,可以根据 XML 配置文件或者 Configuration 类的实例构建该对象。
- 然后获取 SqlSessionFactory 对象,该对象实例可以通过 SqlSessionFactoryBuilder 对象来获取。
- 有了 SqlSessionFactory 对象之后,就可以进而获取 SqlSession 实例。SqlSession 对象中完全包含以数据库为背景的所有执行 SQL 操作的方法,用该实例可以直接执行已映射的 SQL 语句。
1.SqlSessionFactoryBuilder
SqlSessionFactoryBuilder 会根据配置信息或者代码生成 SqlSessionFactory,并且提供了多个 build() 方法重载,如图。
- 通过源码分析,可以发现以上方法都是在调用同一签名方法,即:build(Reader reader, String environment, Properties properties)
- 通过上述分析,发现配置信息可以以三种形式提供给 SqlSessionFactoryBuilder 的 build() 方法,分别是 InputStream(字节流)、Reader(字符流)、Configuration(类)。
- 由于字节流和字符流都属于读取配置文件的方式,所以就很容易想到构建一个 SqlSessionFactory 有两种方式,即:读取 XML 配置文件和编写代码。一般习惯为采取 XML 配置文件的方式来构造 SqlSessionFactory,这样一方面可以避免硬编码,另一方面方便日后配置人员修改,避免重复编译代码。
- SqlSessionFactoryBuilder的生命周期和作用域
SqlSessionFactoryBuilder 的最大特点就是用过即丢。创建 SqlSessionFactory 对象之后,这个类就不存在了,因此 SqlSessionFactoryBuilder 的最佳范围就是存在于方法体内,也就是局部变量。
2.SqlSessionFactory
- SqlSessionFactory 是工厂接口而不是现实类,他的任务就是创建 SqlSession
- 所有的 MyBatis 应用都以 SqlSessionFactory 实例为中心,SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 对象来获取。有了它以后,顾名思义,就可以通过 SqlSession 提供的 openSession() 方法来获取 SqlSession 实例。源码如下。
- SqlSessionFactory的生命周期和作用域
- SqlSessionFactory 对象一旦创建,就会在整个应用程序过程中始终存在。没有理由去销毁或再创建它,并且在应用程序运行中也不建议多次创建 SqlSessionFactory。因此 SqlSessionFactory 的最佳作用域是 Application,即随着应用程序的生命周期一直存在。这种“存在于整个应用运行期间,并且只存在一个对象实例”的模式就是所谓的单例模式(指在运行期间有且仅有一个实例)。
3.SqlSession
- SqlSession 是用于执行持久化操作的对象,类似于 JDBC 中的 Connection。它提供了面向数据库执行 SQL 命令所需的所有方法,可以通过 SqlSession 实例直接运行已映射的 SQL 语句。
- SqlSession 的用途主要有两种。
获取映射器。让映射器通过命名空间和方法名称找到对应的 SQL,并发送给数据库,执行后返回结果。
直接通过“命名空间(namespace)+SQL id”的方式执行 SQL,不需要获取映射器。这是 iBatis 版本留下的方式。例如《第一个MyBatis程序》一节的示例就是这种方式执行的 SQL 语句。- SqlSession生命周期和作用域
- SqlSession 对应一次数据库会话。由于数据库会话不是永久的,因此 SqlSession 的生命周期也不是永久的,每次访问数据库时都需要创建 SqlSession 对象。
- 需要注意的是:每个线程都有自己的 SqlSession 实例,SqlSession 实例不能被共享,也不是线程安全的。因此 SqlSession 的作用域范围是 request 作用域或方法体作用域内。
5.MyBatis配置文件(mybatis-config.xml)
DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties />
<settings />
<typeAliases />
<typeHandlers />
<objectFactory />
<plugins />
<environments>
<environment>
<transactionManager />
<dataSource />
environment>
environments>
<databaseIdProvider />
<mappers />
configuration>
- mybatis-config.xml 文件中的元素节点是有一定顺序的,节点位置必须按以上位置排序,否则会编译错误。
1.configuration 元素
- 是整个 XML 配置文件的根节点,其角色就相当于是 MyBatis 的总管,MyBatis 所有的配置信息都会存放在它里面。
2. properties
- 标签可以通过 resource 属性指定外部 properties 文件(database.properties),也可以通过 properties 子元素配置。
- 指定文件,使用 properties 指定外部文件,代码如下。
<properties resource="mybatisDemo/resources/database.properties"/>
- database.properties 用于描述数据库连接的相关配置,例如数据库驱动、连接数据库的 url、数据库用户名、数据库密码等。
- 2.properties子元素配置
- 通过 properties 子元素 property 配置 username 和 password 变量,然后在 environments 节点中引用这些变量,代码如下。
<properties>
<property name="username" value="root"/>
<property name="password" value="root"/>
properties>
- 在 environments 节点中引用 username 和 password 变量。
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
dataSource>
environment>
environments>
- 也可以不使用 properties 标签,直接将属性值写在 value 中。
3.setting标签
- settings 标签用于配置 MyBatis 的运行时行为,它能深刻的影响 MyBatis 的底层运行,一般不需要大量配置,大部分情况下使用其默认值即可。
- settings 的配置项很多,但是真正用到的不会太多,我们把常用的配置项研究清楚就可以了。settings 配置项说明如下表所示(表中红色字体的配置项为常用配置项)。
- 配置项 作用 配置选项 默认值
- cacheEnabled 该配置影响所有映射器中配置缓存的全局开关 true|false true
- lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。在特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态 true|false false
- aggressiveLazyLoading 当启用时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载;反之,每种属性将会按需加载 true|false 版本3.4.1 (不包含)
之前默认值为 true,之后为 false- multipleResultSetsEnabled 是否允许单一语句返回多结果集(需要兼容驱动) true|false true
- useColumnLabel 使用列标签代替列名。不同的驱动会有不同的表现,具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果 true|false true
- useGeneratedKeys 允许JDBC 支持自动生成主键,需要驱动兼容。如果设置为 true,则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby) true|false false
- autoMappingBehavior 指定 MyBatis 应如何自动映射列到字段或属性。
NONE 表示取消自动映射。
PARTIAL 表示只会自动映射,没有定义嵌套结果集和映射结果集。
FULL 会自动映射任意复杂的结果集(无论是否嵌套) NONE、PARTIAL、FULL PARTIAL- autoMappingUnkno wnColumnBehavior 指定自动映射当中未知列(或未知属性类型)时的行为。 默认是不处理,只有当日志级别达到 WARN 级别或者以下,才会显示相关日志,如果处理失败会抛出 SqlSessionException 异常 NONE、WARNING、FAILING NONE
- defaultExecutorType 配置默认的执行器。SIMPLE 是普通的执行器;REUSE 会重用预处理语句(prepared statements);BATCH 执行器将重用语句并执行批量更新 SIMPLE、REUSE、BATCH SIMPLE
- defaultStatementTimeout 设置超时时间,它决定驱动等待数据库响应的秒数 任何正整数 Not Set (null)
- defaultFetchSize 设置数据库驱动程序默认返回的条数限制,此参数可以重新设置 任何正整数 Not Set (null)
- safeRowBoundsEnabled 允许在嵌套语句中使用分页(RowBounds)。如果允许,设置 false true|false false
- safeResultHandlerEnabled 允许在嵌套语句中使用分页(ResultHandler)。如果允许,设置false true|false true
- mapUnderscoreToCamelCase 是否开启自动驼峰命名规则映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射 true|false false
- localCacheScope
MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速联复嵌套査询。
默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlScssion 的不同调用将不会共享数据 SESSION|STATEMENT SESSION- jdbcTypeForNull
当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER NULL、VARCHAR、OTHER OTHER- lazyLoadTriggerMethods
指定哪个对象的方法触发一次延迟加载 — equals、clone、hashCode、toString- defaultScriptingLanguage
指定动态 SQL 生成的默认语言 — org.apache.ibatis
.script.ing.xmltags
.XMLDynamicLanguageDriver- callSettersOnNulls
指定当结果集中值为 null 时,是否调用映射对象的 setter(map 对象时为 put)方法,这对于 Map.kcySet() 依赖或 null 值初始化时是有用的。注意,基本类型(int、boolean 等)不能设置成 null true|false false- logPrefix
指定 MyBatis 增加到日志名称的前缀 任何字符串 Not set- loglmpl
指定 MyBatis 所用日志的具体实现,未指定时将自动査找 SLF4J|LOG4J|LOG4J2|JDK_LOGGING
|COMMONS_LOGGING
|ST DOUT_LOGGING|NO_LOGGING Not set- proxyFactory 指定 MyBatis 创建具有延迟加栽能力的对象所用到的代理工具 CGLIB|JAVASSIST JAVASSIST (MyBatis 版本为 3.3 及以上的)
- vfsImpl 指定 VFS 的实现类 提供 VFS 类的全限定名,如果存在多个,可以使用逗号分隔 Not set
- useActualParamName 允许用方法参数中声明的实际名称引用参数。要使用此功能,项目必须被编译为 Java 8 参数的选择。(从版本 3.4.1 开始可以使用) true|false true
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
settings>
4.typeAliases标签
- 为了不在任何地方都指定类的全限定名,我们可以使用 typeAliases 标签定义一个别名。
- 例如,在 net.bianchengbang.po 包中有一个 Student 类,则该类的全限定名称为 net.bianchengbang.po.Student。使用 typeAliases 标签定义别名,这样就不用每次都书写类的全限定名称了,代码如下。
<typeAliases>
<typeAlias alias = "Student" type = "net.bianchengbang.po.Student"/>
typeAliases>
- 如果需要对同一个包下的多个类定义别名,则可以定义为:
<typeAliases>
<package name="net.biancheng.po"/>
typeAliases>
- 这样 MyBatis 将扫描 net.biancheng.po 包里面的类,将其第一个字母变为小写作为其别名,例如 Student 别名为 student,User 别名为 user。
5.typeHandlers标签
- typeHandlers 主要将获取的值合理地转化为 Java 类型。在 typeHandler 中,分为 jdbcType 和 javaType,其中 jdbcType 用于定义数据库类型,而 javaType 用于定义 Java 类型,typeHandler 的作用就是承担 jdbcType 和 javaType 之间的相互转换。
- MyBatis 支持自定义处理类型,在自定义处理类型时,需要实现 org.apache.ibatis.type.TypeHandler 接口或继承 org.apache.ibatis.type.BaseTypeHandle 类。
6.environments标签
- 在 environments 标签中,可以配置 MyBatis 的多套运行环境,将 SQL 映射到多个不同的数据库上。
- environment 是 environments 的子标签,用来配置 MyBatis 的一套运行环境,需指定运行环境 ID、事务管理、数据源配置等相关信息。
- 我们可以通过配置多个 environment 标签来连接多个数据库,需要注意的是必须指定其中一个为默认运行环境(通过default指定)。
- environment 标签提供了两个子标签,即 transactionManager 和 dataSource。
- transactionManager标签
- MyBatis 支持两个事务管理器,即 JDBC 和 MANAGED。
- 如果使用 JDBC 类型的事务管理器,则应用程序服务器负责事务管理操作,例如提交、回滚等。如果使用 MANAGED 类型的事务管理器,则应用程序服务器负责管理连接生命周期。
- dataSource标签
- 用于配置数据库的连接属性,例如要连接的数据库的驱动程序名称、URL、用户名和密码等。
- dataSource 中的 type 属性用于指定数据源类型,有以下 3 种类型。
- 1.UNPOOLED
UNPOOLED 没有数据库连接池,效率低下。MyBatis 需要打开和关闭每个数据库操作的连接,它有点慢,通常应用于简单的应用程序。- 2.POOLED
对于 POOLED 数据源类型,MyBatis 将维护一个数据库连接池。并且对于每个数据库的操作,MyBatis 都会使用连接池中的连接,并在操作完成后将它们返回到池中。减少了创建新连接所需的初始连接和身份验证时间。- 3.JNDI
对于 JNDI 的数据源类型,MyBatis 将从 JNDI 数据源中获取连接
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url"
value="jdbc:mysql://localhost:3306/test?characterEncoding=utf8" />
<property name="username" value="root" />
<property name="password" value="root" />
dataSource>
7.mappers标签
- mappers 标签用于指定 MyBatis SQL 映射文件的路径。
- mapper 是 mappers 的子标签,mapper 中的 resource 属性用于指定 SQL 映射文件的路径(类资源路径)
- 例如,SQL 映射文件的名称是 Student.xml,它位于名为 net.biancheng.mapper 的包中,那么您可以这样配置:
<mappers>
<mapper resource="net/biancheng/mapper/Student.xml"/>
mappers>
6.MyBatis Mapper(映射器)
- 映射器是 MyBatis 中最重要的文件,文件中包含一组 SQL 语句(例如查询、添加、删除、修改),这些语句称为映射语句或映射 SQL 语句。
- 映射器由 Java 接口和 XML 文件(或注解)共同组成,它的作用如下。
- 1.定义参数类型
- 2.配置缓存
- 3.提供 SQL 语句和动态 SQL
- 4.定义查询结果和 POJO 的映射关系
- 映射器有以下两种实现方式。
- 1.通过 XML 文件方式实现,比如我们在 mybatis-config.xml 文件中描述的 XML 文件,用来生成 mapper。
- 2.通过注解的方式实现,使用 Configuration 对象注册 Mapper 接口。
- 如果 SQL 语句存在动态 SQL 或者比较复杂,使用注解写在 Java 文件里可读性差,且增加了维护的成本。所以一般建议使用 XML 文件配置的方式,避免重复编写 SQL 语句。
1.XML实现映射器
- XML 定义映射器分为两个部分:接口和XML。下面先定义接口 WebsiteMapper
package net.biancheng.mapper;
import java.util.List;
import net.biancheng.po.Website;
public interface WebsiteMapper {
public List<Website> selectAllWebsite();
}
WebsiteMapper.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="net.biancheng.mapper.WebsiteMapper">
<!-- 查询所有网站信息 -->
<select id="selectAllWebsite"
resultType="net.biancheng.po.Website">
select * from website
</select>
</mapper>
- 下面对上述 XML 文件进行讲解。
- namespace 用来定义命名空间,该命名空间和定义接口的全限定名一致。
- 元素表明这是一条查询语句,属性 id 用来标识这条 SQL。resultType 表示返回的是一个 Website 类型的值。
- 在 MyBatis 配置文件中添加以下代码。
<mapper resource="net/biancheng/mapper/WebsiteMapper.xml" />
- 该语句用来引入 XML 文件,MyBatis 会读取 WebsiteMapper.xml 文件,生成映射器。
- 下面进行测试,用 SqlSession 来获取 Mapper,Test 类代码如下。
public class Test {
public static void main(String[] args) throws IOException {
InputStream config = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(config);
SqlSession ss = ssf.openSession();
WebsiteMapper websiteMapper = ss.getMapper(WebsiteMapper.class);
List<Website> websitelist = websiteMapper.selectAllWebsite();
for (Website site : websitelist) {
System.out.println(site);
}
ss.commit();
ss.close();
}
}
DEBUG [main] - ==> Preparing: select * from website
DEBUG [main] - ==> Parameters:
DEBUG [main] - <== Total: 1
Website[id=1,name=编程帮,url=https://www.biancheng.net/,age=21,country=CN,createtime=Tue Feb 23 10:20:40 CST 2021]
- 注解实现映射器
- 使用注解的方式实现映射器,只需要在接口中使用 Java 注解,注入 SQL 即可。如下所示。
package net.biancheng.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Select;
import net.biancheng.po.Website;
public interface WebsiteMapper2 {
@Select(value = "select * from website")
public List<Website> selectAllWebsite();
}
- 这里我们使用了 @Select 注解,并且注入了和 XML 中相同的 select 语句。
- 如果使用注解和 XML 文件两种方式同时定义,那么 XML 方式将覆盖掉注解方式。
- 虽然这里注解的方式看起来比 XML 简单,但是现实中我们遇到的 SQL 会比该例子复杂得多。如果 SQL 语句中有多个表的关联、多个查询条件、级联、条件分支等,显然这条 SQL 就会复杂的多,所以并不建议读者使用这种方式。比如下面这条 SQL。
- 如果把以上 SQL 放到 @Select 注解中,无疑会大大降低代码的可读性。如果同时还要考虑使用动态 SQL 或需要加入其他的逻辑,这样就使得这个注解更加复杂了,不利于日后的维护和修改。
- 此外,XML 可以相互引入,而注解是不可以的,所以在一些比较复杂的场景下,使用 XML 方式会更加灵活和方便。因此大部分的企业都以 XML 为主,本教程也会保持一致,以 XML 方式来创建映射器。当然在一些简单的表和应用中使用注解方式也会比较简单。
- 这个接口可以在 XML 中定义,将在 mybatis-config.xml 中配置 XML 的语句修改为以下语句即可
<mapper resource="com/mybatis/mapper/WebsiteMapper2" />
也可以使用 configuration 对象注册这个接口,比如:
configuration.addMapper(WebsiteMapper2.class);
2.MyBatis 映射器的主要元素
- 元素名称 描述 备注
- mapper 映射文件的根节点,只有 namescape 一个属性 namescape 作用如下:
用于区分不同的 mapper,全局唯一
绑定DAO接口,即面向接口编程。当 namescape 绑定某一接口后,可以不用写该接口的实现类,MyBatis 会通过接口的完整限定名查找到对应的 mapper 配置来执行 SQL 语句。因此 namescape 的命名必须要跟接口同名。- select 查询语句,最常用、最复杂的元素之一 可以自定义参数,返回结果集等
- insert 插入语句 执行后返回一个整数,代表插入的条数
- update 更新语句 执行后返回一个整数,代表更新的条数
- delete 删除语句 执行后返回一个整数,代表删除的条数
- parameterMap 定义参数映射关系 即将被删除的元素,不建议使用
- sql 允许定义一部分的 SQL,然后在各个地方引用它 例如,一张表列名,我们可以一次定义,在多个 SQL 语句中使用
- resultMap 用来描述数据库结果集与对象的对应关系,它是最复杂、最强大的元素 提供映射规则
- cache 配置给定命名空间的缓存 -
- cache-ref 其它命名空间缓存配置的引用
- 关于 MyBatis 的 SQL 映射文件中的 mapper 元素的 namescape 属性有如下要求。
- namescape 的命名必须跟某个 DAO 接口同名,同属于 DAO 层,因此代码结构上,映射文件与该接口应放置在同一 package 下(如 net.biancheng.dao.website),并且习惯上是以 Mapper 结尾(如 WebsiteMapper.java、WebsiteMapper.xml)。
不同的 mapper 文件中子元素的 id 可以相同,MyBatis 通过 namescape 和子元素的 id 联合区分。接口中的方法与映射文件中的 SQL 语句 id 应一 一对应。
7.MyBatis执行SQL的两种方式
- MyBatis 有两种执行 SQL 语句的方式,如下:
- 1.通过 SqlSession 发送 SQL
- 2.通过 SqlSession 获取 Mapper 接口,通过 Mapper 接口发送 SQL
1.SqlSession发送SQL
- 有映射器之后就可以通过 SqlSession 发送 SQL 了。我们以 getWebsite 这条 SQL 为例看看如何发送 SQL。
Website website = (Website)sqlSession.selectOne("net.biancheng.mapper.WebsiteMapper.getWebsite",1);
- MyBatis 常用的查询方法有 2 种,分别为 selectOne 和 selectList。
- 1.selectOne
- selectOne 方法表示使用查询并且只返回一个对象,必须指定查询条件。只能查询 0 或 1 条记录,大于 1 条记录则运行错误。常用格式如下(也有其它重载方法,根据需要选择)。
sqlSession.selectOne(String arg0, Object arg1)
- 2.selectList
- selectList 方法表示使用查询并且返回一个列表。可以查询 0 或 N 条记录。常用格式如下。
sqlSession.selectOne(String arg0)
- 也可指定参数:
sqlSession.selectList(String arg0, Object arg1)
- 以上语法格式中,String 对象由一个命名空间加 SQL id 组合而成,它完全定位了一条 SQL,这样 MyBatis 就会找到对应的 SQL。Object 对象为需要传递的参数,也就是查询条件。
- selectOne 实现的 selectList 都可以实现,即 list 中只有一个对象。但 selectList 能实现的,selectOne 不一定能实现。
- 如果 MyBatis 中只有一个 id 为 getWbsite 的 SQL,那么也可以简写为:
Website website = (Website )sqlSession.selectOne("getWbsite",1);
- 这是 MyBatis 前身 iBatis 所留下的方式。
2.Mapper接口发送 SQL
- SqlSession 还可以获取 Mapper 接口,通过 Mapper 接口发送 SQL,如下所示。
WebsiteMapper websiteMapper = sqlSession.getMapper(WebsiteMapper.class);
Website website = websiteMapper.getWebsite(1);
* 通过 SqlSession 的 getMapper 方法获取一个 Mapper 接口,然后就可以调用它的方法了。因为 XML 文件或者接口注解定义的 SQL 都可以通过“类的全限定名+方法名”查找,所以 MyBatis 会启用对应的 SQL 运行,并返回结果。
3.区别
- 上面分别讲解了 MyBatis 两种发送 SQL 的方式,一种用 SqlSession 直接发送,另外一种通过 SqlSession 获取 Mapper 接口再发送。笔者建议采用 Mapper 接口发送 SQL 的方式,理由如下:
- 使用 Mapper 接口编程可以消除 SqlSession 带来的功能性代码,提高可读性,而 SqlSession 发送 SQL,需要一个 SQL id 去匹配 SQL,比较晦涩难懂。
- 使用 Mapper 接口,类似 websiteMapper.getWebsite(1) 则是完全面向对象的语言,更能体现业务的逻辑。
- 使用 websiteMapper.getWebsite(1) 方式,IDE 会提示错误和校验,而使用 sqlSession.selectOne(“getWebsite”,1L) 语法,只有在运行中才能知道是否会产生错误。
- 目前使用 Mapper 接口编程已成为主流,尤其在 Spring 中运用 MyBatis 时,Mapper 接口的使用就更为简单,所以本教程使用 Mapper 接口的方式讨论 MyBatis。
8.MyBatis select标签
- 在 MyBatis 中,select 标签是最常用也是功能最强大的 SQL 语言,用于执行查询操作。
- select 示例语句如下。
<select id="selectAllWebsite" resultType="net.biancheng.po.Website" parameterType="string"> SELECT id,NAME,url FROM website WHERE NAME LIKE CONCAT ('%',#{name},'%') </select>
- 以上是一个 id 为 selectAllWebsite 的映射语句,参数类型为 string,返回结果类型为 Website。
- 执行 SQL 语句时可以定义参数,参数可以是一个简单的参数类型,例如 int、float、String;也可以是一个复杂的参数类型,例如 JavaBean、Map 等。MyBatis 提供了强大的映射规则,执行 SQL 后,MyBatis 会将结果集自动映射到 JavaBean 中。
- 为了使数据库的查询结果和返回值类型中的属性能够自动匹配,通常会对 MySQL 数据库和 JavaBean 采用同一套命名规则,即 Java 命名驼峰规则,这样就不需要再做映射了(数据库表字段名和属性名不一致时需要手动映射)。
- 参数的传递使用#{参数名},相当于告诉 MyBatis 生成 PreparedStatement 参数。对于 JDBC,该参数会被标识为“?”。以上 SQL 语句可以使用 JDBC 实现,实现代码如下。
String sql = "SELECT id,NAME,url FROM website WHERE NAME LIKE CONCAT ('%',?,'%')"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1,userName);
1.select标签常用属性
- 属性名称 描 述 备注
- id 它和 Mapper 的命名空间组合起来使用,是唯一标识符,供 MyBatis 调用 如果命名空间+id不唯一,那么 MyBatis 抛出异常
- parameterType 表示传入 SQL 语句传入参数类型的全限定名或别名。它是一个可选属性,MyBatis 能推断出具体传入语句的参数 支持基本数据类型和 JavaBean、Map 等复杂数据类型
- resultType SQL 语句执行后返回的类型(全限定名或者别名)。如果是集合类型,返回的是集合元素的类型,返回时可以使用 resultType 或 resultMap 之一 -
- resultMap 它是映射集的引用,与 元素一起使用,返回时可以使用 resultType 或 resultMap 之一 是 MyBatis 最复杂的元素,可以配置映射规则、级联、typeHandler 等
- flushCache 用于设置在调用 SQL 语句后是否要求 MyBatis 清空之前查询的本地缓存和二级缓存 默认值为 false,如果设置为 true,则任何时候只要 SQL 语句被调用都将清空本地缓存和二级缓存
- useCache 启动二级缓存的开关,默认值为 true,表示将査询结果存入二级缓存中 -
- timeout 用于设置超时参数,单位是秒(s),超时将抛出异常 -
- fetchSize 获取记录的总条数设定 默认值是数据库厂商提供的 JDBC 驱动所设置的条数
- statementType 告诉 MyBatis 使用哪个 JDBC 的 Statement 工作,取值为 STATEMENT(Statement)、 PREPARED(PreparedStatement)、CALLABLE(CallableStatement) -
- resultSetType 这是针对 JDBC 的 ResultSet 接口而言,其值可设置为 FORWARD_ONLY(只允许向前访问)、SCROLL_SENSITIVE(双向滚动,但不及时更新)、SCROLLJNSENSITIVE(双向滚动,及时更新) -
2.传递多个参数
现在需要根据 id 和 name 来模糊查询网站信息,显然这涉及到了两个参数。给映射器传递多个参数分为以下三种方法。
- 1.使用Map传递参数
- 2.使用注解传递参数
- 3.使用JavaBean传递参数
- 使用Map传递参数
- 使用 MyBatis 提供的 Map 接口作为参数实现,如下所示
<!-- 根据name和url模糊查询网站信息 -->
<select id="selectWebsiteByMap" resultType="net.biancheng.po.Website" parameterType="map">
SELECT id,NAME,url FROM website
WHERE name LIKE CONCAT ('%',#{name},'%')
AND url LIKE CONCAT ('%',#{url},'%')
</select>
- 在 WebsiteMapper 接口中,方法如下。
public List<Website> selectWebsiteByMap(Map<String, String> params);
- 测试代码如下。
Map<String,String> paramsMap = new HashMap<String,String>();
paramsMap.put("name","编程");
paramsMap.put("url","biancheng");
websiteMapper.selectWebsiteByMap(paramsMap);
- 使用 Map 传递参数虽然简单易用,但是由于这样设置参数需要键值对应,业务关联性不强,开发人员需要深入到程序中看代码,造成可读性下降。
- 使用注解传递参数
使用 MyBatis 的注解 @Param() 传递参数,如下所示。
<!-- 根据name和url模糊查询网站信息 -->
<select id="selectWebsiteByAn" resultType="net.biancheng.po.Website">
SELECT id,NAME,url FROM website
WHERE name LIKE CONCAT ('%',#{name},'%')
AND url LIKE CONCAT ('%',#{url},'%')
</select>
- WebsiteMapper 接口中方法如下。
public List<Website> selectWebsiteByAn(@Param("name") String name, @Param("url") String url);
- 当我们把参数传递给后台时,MyBatis 通过 @Param 提供的名称就会知道 #{name} 代表 name 参数,提高了参数可读性。但是如果这条 SQL 拥有 10 个参数的查询,就会造成可读性下降,增强了代码复杂性。
3.使用JavaBean传递参数
- 在参数过多的情况下,MyBatis 允许组织一个 JavaBean,通过简单的 setter 和 getter 方法设置参数,提高可读性。如下所示。
<!-- 根据name和url模糊查询网站信息 -->
<select id="selectWebsiteByAn" resultType="net.biancheng.po.Website">
SELECT id,NAME,url FROM website
WHERE name LIKE CONCAT ('%',#{name},'%')
AND url LIKE CONCAT ('%',#{url},'%')
</select>
- WebsiteMapper 接口中方法如下。
public List<Website> selectWebsiteByAn(Website website);
- 这就是通过 JavaBean 传递多个参数的方式。
3.区别
以上 3 种方式的区别如下。
- 使用 Map 传递参数会导致业务可读性的丧失,继而导致后续扩展和维护的困难,所以在实际应用中我们应该果断废弃该方式。
- 使用 @Param 注解传递参数会受到参数个数的影响。当 n≤5 时,它是最佳的传参方式,因为它更加直观;当 n>5 时,多个参数将给调用带来困难。
- 当参数个数大于 5 个时,建议使用 JavaBean 方式。
9.MyBatis insert标签
- MyBatis insert 标签用来定义插入语句,执行插入操作。当 MyBatis 执行完一条插入语句后,就会返回其影响数据库的行数。
- 下面通过一个示例演示 insert 标签的具体用法。
- 在 WebsiteMapper 接口中定义一个 add() 方法,代码如下。
public int addWebsite(String name);
- 参数为 Sting 类型的字符串;返回值为 int 类型,即执行 SQL 后,插入记录的行数。
- 修改 WebsiteMapper.xml,增加插入语句,代码如下。
<!-- 增加网站信息 -->
<insert id="addWebsite" parameterType="string">
insert into website(name)
values(#{name})
</insert>
- 测试代码如下。
//插入 name 为编程帮4 的记录
String name = "编程帮4";
int i = websiteMapper.addWebsite(name);
System.out.println("共插入了 " + i + " 条记录");
- 执行测试代码,控制台输出如下。
共插入了 1 条记录
1.insert 标签常用属性
- 属性名称 描述 备注
- id 它和 Mapper 的命名空间组合起来使用,是唯一标识符,供 MyBatis 调用 如果命名空间+ id 不唯一,那么 MyBatis 抛出异常
- parameterType 传入 SQL 语句的参数类型的全限定名或别名,它是一个可选属性。 支持基本数据类型和 JavaBean、Map 等复杂数据类型
- keyProperty 该属性的作用是将插入操作的返回值赋给 PO 类的某个属性,通常为主键对应的属性。如果是联合主键,可以将多个值用逗号隔开。 -
- useGeneratedKe 该属性用来设置,是否使用 JDBC 提供的 getGenereatedKeys() 方法,获取数据库内部产生的主键并赋值到 keyProperty 属性设置的请求对象的属性中,例如 MySQL、SQL Server 等自动递增的字段,其默认值为 false。 该属性值设置为 true 后,会将数据库生成的主键回填到请求对象中,以供其他业务使用。
- flushCache 该属性用于设置执行该操作后,是否会清空二级缓存和本地缓存,默认值为 true。 -
- timeout 该属性用于设置执行该操作的最大时限,如果超时,就抛异常。 -
- databaseId 取值范围 oracle、mysql 等,表示数据库厂家;元素内部可通过 来为特定数据库指定不同的 sql 语句。 MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。 MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库 databaseId 属性的所有语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。
- keyColumn 该属性用于设置第几列是主键,当主键列不是表中的第 1 列时,就需要设置该属性。如果是联合主键,可以将多个值用逗号隔开。
*注意:insert 标签中没有 resultType 属性,只有查询操作才需要对返回结果类型进行相应的指定。
2.传递多个参数
传递多个参数
在上面的示例中,我们在插入语句中只使用了一个 String 类型的参数,而在实际的开发过程中,我们的插入语句往往需要使用多个参数,Mybatis 为我们提供以下 3 种方式,来实现给映射器传递多个参数:
使用 Map 传递参数
使用注解传递参数
使用 JavaBean 传递参数
使用 Map 传递参数
我们可以将参数封装到一个 Map 对象中,然后传递给 MyBatis 的映射器,示例如下。
3.主键(自动递增)回填
我们知道,MySQL、SQL Server 等数据库表可以采用自动递增的字段作为其主键,当向这样的数据库表插入数据时,即使不指定自增主键的值,数据库也会根据自增规则自动生成主键并插入到表中。
一些特殊情况下,我们可能需要将这个刚刚生成的主键回填到请求对象(原本不包含主键信息的请求对象)中,供其他业务使用。此时,我们就可以通过在 insert 标签中添加 keyProperty 和 useGeneratedKeys 属性,来实现该功能。
下面我们通过一个示例,来演示主键(自动递增)回填功能。
4.自定义主键
如果在实际项目中,若数据库不支持主键自动递增(例如 Oracle),或者取消了主键自动递增的规则,我们可以使用 MyBatis 的 标签自定义生成主键,具体配置代码如下。
select if(max(id) is null,1,max(id)+1) as newId from Website insert into Website (id,name,url) values(#{id},#{name},#{url}) 标签中属性说明如下: keyProperty:用于指定主键值对应的 PO 类的属性。 order:该属性取值可以为 BEFORE 或 AFTER。BEFORE 表示先执行 标签内的语句,再执行插入语句;AFTER 表示先执行插入语句再执行 标签内的语句。10.MyBatis update标签
- MyBatis update 标签用于定义更新语句,执行更新操作。当 MyBatis 执行完一条更新语句后,会返回一个整数,表示受影响的数据库记录的行数。
1.update 标签常用属性
属性名称 描述 备注
id 它和 Mapper 的命名空间组合起来使用,是唯一标识符,供 MyBatis 调用 如果命名空间+ id 不唯一,那么 MyBatis 抛出异常
parameterType 传入 SQL 语句的参数类型的全限定名或别名,它是一个可选属性。 支持基本数据类型和 JavaBean、Map 等复杂数据类型
flushCache 该属性用于设置执行该操作后,是否会清空二级缓存和本地缓存,默认值为 true。 -
timeout 该属性用于设置 SQL 执行的超时时间,如果超时,就抛异常。 -
statementType 执行 SQL 时使用的 statement 类型, 默认为 PREPARED,可选值:STATEMENT,PREPARED 和 CALLABLE。
注意:update 标签中没有 resultType 属性,只有查询操作才需要对返回结果类型进行相应的指定。
2.传递多个参数
在上面的示例中,我们在更新语句中只使用了一个 String 类型的参数,而在实际的开发过程中,大多数时候,我们的更新语句都需要使用多个参数,Mybatis 为我们提供以下 3 种方式,来实现给映射器传递多个参数:
使用 Map 传递参数
使用注解传递参数
使用 JavaBean 传递参数
使用 Map 传递参数
我们可以将参数封装到一个 Map 对象中,然后传递给 MyBatis 的映射器,示例如下。
11.MyBatis delete标签
- MyBatis delete 标签用于定义 delete 语句,执行删除操作。当 MyBatis 执行完一条更新语句后,会返回一个整数,表示受影响的数据库记录的行数。
下面我们通过一个示例演示 delete 标签的用法。
1.delete标签常用属性
属性名称 描述 备注
id 它和 Mapper 的命名空间组合起来使用,是唯一标识符,供 MyBatis 调用 如果命名空间+ id 不唯一,那么 MyBatis 抛出异常
parameterType 传入 SQL 语句的参数类型的全限定名或别名,它是一个可选属性。 支持基本数据类型和 JavaBean、Map 等复杂数据类型
flushCache 该属性用于设置执行该操作后,是否会清空二级缓存和本地缓存,默认值为 true。 -
timeout 该属性用于设置 SQL 执行的超时时间,如果超时,就抛异常。 -
statementType 执行 SQL 时使用的 statement 类型, 默认为 PREPARED,可选值:STATEMENT,PREPARED 和 CALLABLE。 -
注意:delete 标签中没有 resultType 属性,只有查询操作才需要对返回结果类型进行相应的指定。
3.传递多个参数
在上面的示例中,我们在 delete 语句中只使用了一个 String 类型的参数,而在实际的开发过程中,我们的 delete 语句往往需要使用多个参数对记录进行筛选,Mybatis 为我们提供以下 3 种方式,来实现给映射器传递多个参数:
使用 Map 传递参数
使用注解传递参数
使用 JavaBean 传递参数
使用 Map 传递参数
我们可以将参数封装到一个 Map 对象中,然后传递给 MyBatis 的映射器,示例如下。
12.Mybatis resultMap元素
Mybatis-Plus
- Mybatis-Plus(简称 MP)是 Mybatis 的增强工具,在 Mybatis 的基础上只做增强不做改变,支持 Mybatis 所有原生的特性,为简化开发、提高效率而生。有兴趣的小伙伴可以参考 MyBatis-Plus 官网。