Spring学习笔记
本笔记为本人在B站学习时整理而成,为了更好的学习,将其整理成笔记,以防忘记相关知识点。
Spring概述
概述
Spring:
出现在2002年左右,降低企业级开发难度。帮助进行模块之间、类与类之间的管理,帮助开发人员创建对象,管理对象之间的关系。
2003年传入国内,被大量使用。
2017出现新的流行框架SpringBoot,核心思想与Spring相同。
核心技术:
IoC、AOP,能使模块之间、类之间解耦合。
依赖:
class A使用class B的属性或方法,称之为class A依赖class B。
官网:
https://spring.io/
优点
(1)轻量:Spring的所需要的jar包都非常小,一般1M以下,几百kb。核心功能所需要的jar包总共3M左右。Spring框架运行占有资源少,运行效率高,不依赖其他jar。
(2) 针对接口编程,解耦合
(3) AOP 编程的支持
(4) 方便集成各种优秀框架
(2)(3)(4)的优点将会在接下来学习中体会。
Spring 体系结构
Spring 由 20 多个模块组成,它们可以分为数据访问/集成(Data Access/Integration)、 Web、面向切面编程(AOP, Aspects)、提供JVM的代理 (Instrumentation)、消息发送(Messaging)、 核心容器(Core Container)和测试(Test)。
spring全家桶:
spring , springmvc ,spring boot , spring cloud
框架怎么学: 框架是一个软件,其它人写好的软件。
1)知道框架能做什么, mybatis能访问数据库, 对表中的数据执行增删改查。
2)框架的语法, 框架要完成一个功能,需要一定的步骤支持的,
3)框架的内部实现, 框架内部怎么做。 原理是什么。
4)通过学习,可以实现一个框架。
IoC控制反转
控制反转(IoC,Inversion of Control),是一个概念,是一种思想。指将传统上由程序代 码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对 象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现对象的创建,属性赋值, 依赖的管理。
Ioc 的实现:
➢ 依赖查找:DL( Dependency Lookup ),容器提供回调接口和上下文环境给组件。
➢ 依赖注入:DI(Dependency Injection),程序代码不做定位查询,这些工作由容器自行完成。
Spring 框架使用依赖注入(DI)实现 IoC。
Spring 容器是一个超级大工厂,负责创建、管理所有的 Java 对象,这些 Java 对象被称为 Bean。
Spring 容器管理着容器中 Bean 之间的依赖关系,Spring 使用“依赖注入”的方式 来管理 Bean 之间的依赖关系。使用 IoC 实现对象之间的解耦合。
控制反转的理解
控制反转, 是一个理论,概念,思想。
控制: 创建对象,对象的属性赋值,对象之间的关系管理。
正转:由开发人员在代码中,使用new 构造方法创建对象, 开发人员主动管理对象。
把原来的开发人员管理,创建对象的权限转移给代码之外的容器实现。 由容器代替开发人员管理对象,创建对象,给属性赋值。
为什么要使用 ioc : 目的就是减少对代码的改动, 也能实现不同的功能。 实现解耦合。
底层实现是反射机制。
Spring的第一个程序
实现步骤:
(1)添加module,新建maven工程,选择maven-archetype-quickstart
骨架模板
(2)填写工程坐标:
GroupId:com.kwxy
ArtifactId:ch01-hello-spring
Version:1.0-SNAPSHOT
(3)Module name设置为:ch01-hello-spring,点击finish,等待maven项目的构建,控制台显示BUILD SUCCESS字样,则构建成功
(4) 将src/main
和src/test
下的java
目录右键Mark Directory as-->Sources Root
(5)在src/main
下新建resources
目录,右键Mark Directory as-->Resources Root
(6)删除默认创建的APP类文件 ,这些文件分别在main和test目录中
(7)删除pom.xml文件中无关的配置,将编译和运行jdk版本改为1.8
(8)在build标签
中添加’指定资源位置’的配置,配置大致如下:
<?xml version="1.0" encoding="UTF-8"?><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.0</modelVersion><groupId>com.kwxy</groupId><artifactId>ch01-hello-spring</artifactId><version>1.0-SNAPSHOT</version><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><dependencies><!--单元测试--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version><scope>test</scope></dependency></dependencies><build><resources><resource><directory>src/main/java</directory><!--所在的目录--><includes><!--包括目录下的.properties,.xml 文件都会扫描到--><include>**/*.properties</include><include>**/*.xml</include></includes><filtering>false</filtering></resource></resources></build>
</project>
(9)添加Spring依赖(加完最好右键pom.xml->Maven->Reimport
)
<!--Spring的依赖-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.4.RELEASE</version>
</dependency>
<!--
添加spring-context依赖后,会自动关联以下jar
spring-aop
spring-beans
spring-core
spring-expression
-->
(10)创建业务接口和实现类
接口:
package com.kwxy.service;public interface SomeService {void doSome();
}
实现类:
package com.kwxy.service.impl;import com.kwxy.service.SomeService;public class SomeServiceImpl implements SomeService {/*** spring默认调用无参数构造方法创建对象。* 如果没有无参数构造方法,报错:No default constructor found*/public SomeServiceImpl() {System.out.println("SomeServiceImpl的无参数构造方法");}@Overridepublic void doSome() {System.out.println("执行了SomeServiceImpl的doSome()方法");}
}
(11)创建Spring配置文件
如同在Servlet中我们需要在web.xml中注册我们希望服务器自动创建管理的servlet对象一样,在Spring中也需要有类似的配置,来自动创建刚才的SomeServiceImpl对象。
在 src/main/resources/目录现创建一个 xml 文件,文件名可以随意,但 Spring 建议的名称为 applicationContext.xml
。
IDEA已经为我们设计好了Spring配置文件的模板:右击resources–>new–>XML configuration file–>Spring Config
模板如下:
<?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"></beans>
注意
,Spring 配置文件中使用的约束文件为 xsd 文件。作用与Mybatis的sql映射文件的dtd约束文件类似,但xsd约束作用更强:
1)定义一个 xml 文档中都有什么元素
2)定义一个 xml 文档中都有什么属性
3)定义一个 xml 文档中元素可以有哪些子元素,以及元素的顺序
4)定义一个 xml 文档中元素和属性的数据类型
<beans/>
是配置文件的根标签。在Spring中,java对象称之为bean。在这个标签下进行java对象的注册:
<bean id="someService" class="com.kwxy.service.SomeServiceImpl" scope="prototype"/>
1.声明java对象交给Spring创建和管理,这个步骤称之为声明bean
。
等同于:
SomeService someService = new com.kwxy.service.SomeServiceImpl();
然后将创建的对象是放入到Spring的容器(Map<id,对象>):
private final Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap(16);
factoryBeanObjectCache.put(“service”,someService);
一个bean标签声明一个对象。
2.<bean/>
标签的属性:
class:类的全限定名称,不能是接口(Spring使用反射创建对象);
class可以是非自定义的对象,例如”java.util.Date“,依然可以被Spring创建对象
。
id:自定义的对象名称,要求是唯一值。 表示在Spring中的对象名称,通过这个名称可以从Spring中找到对象,获取对象。
scope:指定bean对象的作用域(对象的存在范围和可见性)。
scope的可取值为:
1)单例
:singleton
, 默认值,表示叫这个名称的对象在spring容器中只有一个
。
2)原型
:prototype
, 表示每次使用getBean()都创建一个新的对象
。
(12)新建测试类,在测试类中创建测试方法
public class MyTest {@Testpublic void test01(){//定义Spring的配置文件, 配置文件是在类路径的根目录之下String config = "applicationContext.xml";//创建Spring的容器对象.根据Spring配置文件的位置,使用接口的不同实现类//1.如果Spring的配置文件是在类路径(classpath),使用ClassPathXmlApplicationContext//2.如果Spring的配置文件是放在项目的根之下(与src、target同级目录),使用FileSystemXmlApplicationContext//创建Spring容器,会读取配置文件中的bean标签,并创建对应的对象。//ClassPathXmlApplicationContext:表示从类路径中加载spring的配置文件ApplicationContext ac = new ClassPathXmlApplicationContext(config);//从容器中获取对象 使用getBean("<bean>的id")SomeService service = (SomeService) ac.getBean("someService");//调用业务方法service.doSome();}/*** 获取spring容器中 java 对象的信息*/@Testpublic void test02(){String config="applicationContext.xml";ApplicationContext ac = new ClassPathXmlApplicationContext(config);//使用spring提供的方法, 获取容器中定义的对象的数量int nums = ac.getBeanDefinitionCount();System.out.println("容器中定义的对象数量:"+nums);//容器中每个定义的对象的名称String names [] = ac.getBeanDefinitionNames();for(String name:names){System.out.println(name);}}
}
Bean的装配
默认装配方式
当我们创建ApplicationContext对象时,Spring会读取配置文件的<bean/>
并执行对应类的无参构造器。在这些类的无参构造器中加一条输出语句可以验证以上结论。
ApplicationContext 容器,会在容器对象初始化时,将其中的所有对象一次性全部装配好。
以后代码中若要使用到这些对象,只需从内存中直接获取即可。执行效率较高。但占用内存。
容器中Bean的作用域
当通过 Spring 容器创建一个 Bean 实例时,不仅可以完成 Bean 的实例化,还可以通过 scope 属性,为 Bean 指定特定的作用域。Spring 支持多种作用域。
(1)singleton:单例模式。即在整个 Spring 容器中,使用 singleton 定义的 Bean 将是单例的,叫这个名称的对象只有一个实例。默认为单例的。
(2)prototype:原型模式。即每次使用 getBean 方法获取的同一个的实例都是一个新的实例。
(3)request:对于每次 HTTP 请求,都将会产生一个不同的 Bean 实例。
(4)session:对于每个不同的 HTTP session,都将产生一个不同的 Bean 实例。
注意:
对于 scope 的值 request、session 只有在 Web 应用中使用 Spring 时,该作用域才有效。
对于 scope 为 singleton 的单例模式,该 Bean 是在容器被创建时即被装配好了;
对于 scope 为 prototype的原型模式,Bean 实例是在代码中使用该 Bean 实例时才进行装配的。
我们只是自动创建了对象,没有给对象的属性赋值。后面介绍两种给属性赋值的方法。
基于XML的DI
通过在xml配置文件对对象的属性进行赋值。
设值注入(掌握)
又称为set注入,通过调用类中属性的setter给属性赋值。
简单类型(java中的基本类型和String类型)
在<bean/>
标签下添加:<property name="属性名" value="简单类型的属性值" />
每一个property标签,完成一个属性的赋值。
注意:
Spring执行property标签的原理,是执行name属性值对应的set方法。而并不关心set方法的具体实现和属性是否真的存在。
引用类型
当指定 bean 的某属性值为另一 bean 的实例时,通过 ref 指定它们间的引用关系。ref 的 值必须为某 bean 的 id 值。
例如:创建一个学校类:
package com.kwxy.domain;public class School {private String name;private String address;public void setName(String name) {this.name = name;}public void setAddress(String address) {this.address = address;}@Overridepublic String toString() {return "School{" +"name='" + name + '\'' +", address='" + address + '\'' +'}';}
}
在学生类中添加School类型的属性:
package com.kwxy.domain;public class Student {private String name;private int age;//引用类型private School school;public Student() {System.out.println("Student的无参数构造方法");}public void setName(String name) {this.name = name;}public void setAge(int age) {this.age = age;}public void setSchool(School school) {System.out.println("setSchool:"+school);this.school = school;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +", school=" + school +'}';}
}
Spring配置文件中添加如下bean:
<!--声明School--><bean id="mySchool" class="com.kwxy.domain.School"><property name="name" value="北京大学" /><property name="address" value="北京的海淀区" /></bean><!--ref作为属性--><bean id="myStudent" class="com.kwxy.domain.Student" ><property name="name" value="李四" /><property name="age" value="20" /><property name="school" ref="mySchool" /> <!--setSchool(mySchool) --></bean><!--ref作为子标签--><bean id="myStudent2" class="com.kwxy.domain.Student"><property name="name" value="张三" /><property name="age" value="22" /><property name="school"><ref bean="mySchool"/></property></bean>
构造注入(理解)
执行类的有参构造,在构造对象的同时给属性赋值。
1.给Student类添加有参构造器:
//定义有参数构造方法
public Student(String name, int age, School school) {System.out.println("Student有参数构造方法");this.name = name;this.age = age;this.school = school;}
2.对xml配置文件进行相关配置:
<!--使用name属性--><bean id="myStu" class="com.kwxy.domain.Student" ><constructor-arg name="age" value="22"/><constructor-arg name="name" value="张三" /><constructor-arg name="school" ref="mySch" /></bean><!--声明School--><bean id="mySch" class="com.kwxy.domain.School"><property name="name" value="江苏师范大学" /><property name="address" value="科文学院" /></bean>
<constructor-arg />
标签中用于指定参数的属性有:
➢ name:指定参数名称,指的是构造方法中的形参。
➢ index:指明该参数对应着构造器的第几个参数,从 0 开始。不过,该属性不要也行,但要注意,若参数类型相同,或之间有包含关系,则需要保证赋值顺序要与构造器中的参数顺序一致。
value:构造方法的形参类型是简单类型的,使用value
ref:构造方法的形参类型是引用类型的,使用ref
引用类型的自动注入
对于引用类型属性的注入,也可不在配置文件中显示的注入。可以通过为<bean/>
标签 设置 autowire 属性值,为引用类型属性进行隐式自动注入(默认是不自动注入引用类型属性)。
根据自动注入判断标准的不同,可以分为两种:
(1)byName:根据名称自动注入
(2)byType: 根据类型自动注入
byName 方式自动注入
当配置文件中被调用者 bean 的 id 值与代码中调用者 bean 类的属性名相同时,可使用 byName 方式,让容器自动将被调用者 bean 注入给调用者 bean。
容器是通过调用者的 bean 类的属性名与配置文件的被调用者 bean 的 id 进行比较而实现自动注入的。
例如,在Student类中,School类型的属性名是”school“,那么在xml配置文件中:
<bean id="mySchool" class="com.kwxy.domain.School" autowire="byName"><property name="name" value="北京大学" /><property name="address" value="北京的海淀区" />
</bean><bean id="myStudent" class="com.kwxy.domain.Student" ><property name="name" value="李四" /><property name="age" value="20" /><!--<property name="school" ref="mySchool" />-->
</bean>
byType 方式自动注入
使用 byType 方式自动注入,要求:配置文件中被调用者 bean 的 class 属性指定的类, 要与代码中调用者 bean 类的某引用类型属性类型同源。
即要么相同,要么有 is-a 关系(子 类,或是实现类)。但这样的同源的被调用 bean 只能有一个。多于一个,容器就不知该匹配哪一个了。
<!--byType(按类型注入) : java类中引用类型的数据类型和spring容器中(配置文件)<bean>的class属性是同源关系的,这样的bean能够赋值给引用类型同源就是一类的意思:1.java类中引用类型的数据类型和bean的class的值是一样的。2.java类中引用类型的数据类型和bean的class的值父子类关系的。3.java类中引用类型的数据类型和bean的class的值接口和实现类关系的语法:<bean id="xx" class="yyy" autowire="byType">简单类型属性赋值</bean>注意:在byType中, 在xml配置文件中声明bean只能有一个符合条件的,多余一个是错误的--><!--byType自动注入--><bean id="myStudent" class="com.kwxy.domain.Student" autowire="byType"><property name="name" value="晶哥" /><property name="age" value="22"/></bean><!--声明School--><!--<bean id="mySchool" class="com.kwxy.domain.School"><property name="name" value="清华大学" /><property name="address" value="北京的海淀区" /></bean>--><!--声明School的子类对象--><bean id="xiaoXueSchool" class="com.kwxy.domain.XiaoXueSchool"><property name="name" value="中兴小学" /><property name="address" value="北京的大兴区"/></bean>
为应用指定多个 Spring 配置文件
在实际应用里,随着应用规模的增加,系统中 Bean 数量也大量增加,导致配置文件变 得非常庞大、臃肿。
为了避免这种情况的产生,提高配置文件的可读性与可维护性,可以将 Spring 配置文件分解成多个配置文件。
包含关系的配置文件: 多个配置文件中有一个总文件,总配置文件将各其它子文件通过<import/>
引入。
在 Java 代码中只需要使用总配置文件对容器进行初始化即可。
例如:有spring-school.xml、spring-student.xml配置文件和一个total.xml总配置文件。
在total.xml中:
<?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"><!--包含关系的配置文件:spring-total表示主配置文件 : 包含其他的配置文件的,主配置文件一般是不定义对象的。语法:<import resource="其他配置文件的路径" />关键字:"classpath:" 表示类路径(class文件所在的目录),在spring的配置文件中要指定其他文件的位置, 需要使用classpath,告诉spring到哪去加载读取文件。--><!--加载的是文件列表--><!--<import resource="classpath:ba06/spring-school.xml" /><import resource="classpath:ba06/spring-student.xml" />--><!--在包含关系的配置文件中,可以通配符(*:表示任意字符),使用通配符的话,配置文件必须放在一个目录中即这个配置文件要放在resources下的一个目录中。这个目录自己去创建,比如下面的ba06。注意: 主的配置文件名称不能包含在通配符的范围内(不能叫做spring-total.xml)--><import resource="classpath:ba06/spring-*.xml" />
</beans>
注意: 主的配置文件名称不能包含在通配符的范围内。
基于注解的DI
通过以下四个步骤实现DI:
(1)创建Maven项目,在pom.xml 加入 AOP 依赖
<dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>5.2.4.RELEASE</version>
</dependency>
如果你在pom.xml中添加了spring-context,那此依赖自动包含spring-aop,无需再次添加。
(2)需要更换配置文件头,加入 spring-context.xsd 约束
约 束 在 %SPRING_HOME%\docs\spring-framework-reference\html\xsd-configuration.html 文件中。
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"></beans>
如果使用IDEA进行编辑,这一步骤可以省略,在执行第四步时根据提示自动添加即可。
(3)在类中添加注解
package com.kwxy.domain;import org.springframework.stereotype.Component;/*** @Component: 创建对象的, 等同于<bean>的功能* 属性:value 就是对象的名称,也就是bean的id值,* value的值是唯一的,创建的对象在整个spring容器中就一个* 位置:在类的上面** @Component(value = "myStudent")等同于* <bean id="myStudent" class="com.kwxy.domain.Student" />** spring中和@Component功能一致,创建对象的注解还有:* 1.@Repository(用在持久层类的上面) : 放在dao的实现类上面,* 表示创建dao对象,dao对象是能访问数据库的。* 2.@Service(用在业务层类的上面):放在service的实现类上面,* 创建service对象,service对象是做业务处理,可以有事务等功能的。* 3.@Controller(用在控制器的上面):放在控制器(处理器)类的上面,创建控制器对象的,* 控制器对象,能够接受用户提交的参数,显示请求的处理结果。* 以上三个注解的使用语法和@Component一样的。 都能创建对象,但是这三个注解还有额外的功能。* @Repository,@Service,@Controller是给项目的对象分层的。* 默认是单例对象*/
@Component("myStudent")
public class Student {private String name;private int age;public Student(String name, int age, School school) {this.name = name;this.age = age;this.school = school;}//引用类型private School school;public Student() {System.out.println("Student的无参数构造方法");}public void setName(String name) {this.name = name;}public void setAge(int age) {this.age = age;}public void setSchool(School school) {System.out.println("setSchool:"+school);this.school = school;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +", school=" + school +'}';}
}
(4)声明组件扫描器。在Spring配置文件的<beans/>
标签下:
<!--声明组件扫描器(component-scan),组件就是java对象base-package:指定注解在你的项目中的包名。component-scan工作方式: spring会扫描遍历base-package指定的包,把包中和子包中的所有类,找到类中的注解,按照注解的功能创建对象,或给属性赋值。加入了component-scan标签,配置文件的变化:1.加入一个新的约束文件spring-context.xsd2.给这个新的约束文件起个命名空间的名称
--><context:component-scan base-package="com.kwxy.domain" /><!--指定多个包的三种方式--><!--第一种方式:使用多次组件扫描器,指定不同的包--><context:component-scan base-package="com.kwxy.domain"/><context:component-scan base-package="com.kwxy.domain2"/><!--第二种方式:使用分隔符(;或,)分隔多个包名--><context:component-scan base-package="com.kwxy.domain;com.kwxy.domain2" /><!--第三种方式:指定父包--><context:component-scan base-package="com.kwxy" />
定义 Bean 的注解@Component(掌握)
@Component: 创建类的对象,等同于<bean />
,默认是单例对象
属性: value 表示对象的名称(bean的id)
位置: 在类定义的上面,表示创建此类的对象。
例如:@Component(value = "myStudent")
等价于<bean id="myStudent" class="com.kwxy.domain.Student"/>
另外,Spring 还提供了 3 个创建对象的注解:
➢ @Repository 用于对 DAO 实现类进行注解
➢ @Service 用于对 Service 实现类进行注解
➢ @Controller 用于对 Controller 实现类进行注解 这三个注解与@Component 都可以创建对象
但这三个注解还有其他的含义,@Service 创建业务层对象,业务层对象可以加入事务功能,@Controller 注解创建的对象可以作为处理器接收用户的请求。
@Repository,@Service,@Controller 是对@Component 注解的细化,标注不同层的对象。即持久层对象,业务层对象,控制层对象。
@Component 不指定 value 属性,bean 的 id 是类名的首字母小写。
若想使用这些注解形式的DI,必须在spring配置文件中声明组件扫描器
:
<context:component-scan base-package="注解类所在包名" />
简单类型属性注入@Value(掌握)
@Component("myStudent")
public class Student {/*** @Value: 简单类型的属性赋值* 属性: value 是String类型的,表示简单类型的属性值* 位置: 1.在属性定义的上面,无需set方法,推荐使用。* 2.在set方法的上面*/@Value("李晶")private String name;private int age;public void setName(String name) {this.name = name;}@Value("23")public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}
byType 自动注入@Autowired(掌握)
需要在引用属性上使用注解@Autowired,该注解默认使用按类型自动装配Bean的方式。
使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加 到 setter 上。
@Autowired:spring框架提供的注解,实现引用类型的赋值,默认使用byType自动注入;
当有多个类型匹配时,使用要注入的对象变量名称作为 bean 的 id,在 spring 容器查找,找到了也可以注入成功。找不到就报错。
即Autowired默认先按byType,如果发现找到多个bean,则,又按照byName方式比对,如果还有多个name相同,则报出异常。
当不确定注入哪个bean的时候,可以联合使用注解@Autowired 与@Qualifier指定注入某个bean的id。
例如School类:
@Component("mySchool")
public class School {@Value("师范大学")private String name;@Value("徐州科文")private String address;public void setName(String name) {this.name = name;}public void setAddress(String address) {this.address = address;}@Overridepublic String toString() {return "School{" +"name='" + name + '\'' +", address='" + address + '\'' +'}';}
}
Student类:
@Component("myStudent")
public class Student {/*** @Value: 简单类型的属性赋值* 属性: value 是String类型的,表示简单类型的属性值* 位置: 1.在属性定义的上面,无需set方法,推荐使用。* 2.在set方法的上面*/@Value("李晶")private String name;@Value("23")private int age;/*** 引用类型* @Autowired: spring框架提供的注解,实现引用类型的赋值。* spring中通过注解给引用类型赋值,使用的是自动注入原理 ,支持byName, byType* @Autowired:默认使用的是byType自动注入。** 位置:1)在属性定义的上面,无需set方法, 推荐使用* 2)在set方法的上面*/@Autowiredprivate School school;public void setName(String name) {this.name = name;}public void setAge(int age) {this.age = age;}public void setSchool(School school) {System.out.println("setSchool:"+school);this.school = school;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +", school=" + school +'}';}
}
为了防止空指针异常
的产生,推荐@AutoWired注解加在带参构造器上。具体原因参见:阿丙博客
byName 自动注入@Autowired 与@Qualifier(掌握)
需要在引用属性上联合使用注解@Autowired 与@Qualifier。@Qualifier 的 value 属性用 于指定要匹配的 Bean 的 id 值。类中无需 set 方法,也可加到 set 方法上。
例如:Student要自动注入School类,只需在school属性前添加注解:
@Autowired
@Qualifier("mySchool")//与School类的@component注解value值相同
private School school;
@Autowired 还有一个属性 required,默认值为 true,表示当匹配失败后,会终止程序运行。若将其值设置为 false,则匹配失败,将被忽略,未匹配的属性值为 null。
JDK 注解@Resource 自动注入(掌握)
Spring提供了对jdk中@Resource注解的支持。
@Resource注解既可以按名称匹配Bean, 也可以按类型匹配 Bean。
默认是按名称注入。使用该注解,要求 JDK 必须是 6 及以上版本。
@Resource 可在属性上,也可在 set 方法上。
本质就是用@Resource一个注解代替@Autowired和@Qualifier两个注解。
(1)byType 注入引用类型属性
@Resource 注解若不带任何参数,采用默认按名称
的方式注入,按名称不能注入bean, 则会按照类型进行 Bean 的匹配注入。
如果Student要自动注入School类,只需在school属性前添加注解:
@Resource
private School school;
(2)byName 注入引用类型属性
@Resource 注解指定其 name 属性,则 name 的值即为按照名称进行匹配的 Bean 的 id。
如果Student要自动注入School类,只需在school属性前添加注解:
@Resource(name = "mySchool")
private School school;
如果JDK是11版本的,学到@Resource注解的时候,加上以下依赖,否则会报错。因为高版本中移除了改模块。
<dependency><groupId>javax.annotation</groupId><artifactId>javax.annotation-api</artifactId><version>1.2</version>
</dependency>
当一个接口有多个实现类时
当一个接口有多个实现时,直接使用@Autowired可能会报异常,原因Autowired默认按照byType注入,找到了多个符合条件的bean,但是不知道注入哪一个,就会报异常。
方式1:
联合使用注解@Autowired 与@Qualifier。@Qualifier 的 value 属性用 于指定要匹配的 Bean 的 id 值。
方式2:
@Resource 注解指定其 name 属性,则 name 的值即为按照名称进行匹配的 Bean 的 id。
注解与 XML 的对比
注解
优点是:
(1)方便
(2)直观
(3)高效(代码少,没有配置文件的书写那么复杂)
其弊端也显而易见:以硬编码的方式写入到 Java 代码中,修改是需要重新编译代码的
XML方式
优点是:
(1)配置和代码是分离的
(2)在 xml 中做修改,无需编译代码,只需重启服务器即可将新的配置加载
xml 的缺点是:编写麻烦,效率低,大型项目过于复杂。
xml与注解方式还有一个区别在于,xml方式可以通过bean标签创建一个类的多个对象,但注解方式并不能,因为一个类不能存在两个同名的注解。
AOP面向切面编程
AOP概述
AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程序运行过程。
AOP 底层,就是采用动态代理模式实现的。
采用了两种代理:JDK 的动态代理,与 CGLIB 的动态代理
,AOP可以看作动态代理的规范化与标准化
。
面向切面编程
,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到 主业务逻辑中。
所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、 事务、日志、缓存等。
若不使用 AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样, 会使主业务逻辑变的混杂不清。
例如,转账,在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事 务等交叉业务逻辑,而这些业务逻辑与主业务逻辑间并无直接关系。
但,它们的代码量所占比重能达到总代码量的一半甚至还多。它们的存在,不仅产生了大量的“冗余”代码,还大大干扰了主业务逻辑—转账。
AOP的相关术语
(1) 切面(Aspect)
切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面 是通知(Advice)。实际就是对主业务逻辑的一种增强。
(2) 连接点(JoinPoint)
连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。
(3) 切入点(Pointcut)
切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。 被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。
(4) 目标对象(Target)
目标对象指将要被增强的对象。即包含主业务逻辑的类的对象。上例中的 StudentServiceImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。当然,不被增强,也就无所谓目标不目标了。
(5) 通知(Advice)
通知是切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。Advice 也叫增强。上例中的 MyInvocationHandler 就可以理解为是一种通知。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。
切入点定义切入的位置,通知定义切入的时间。
AOP的实现
AOP的技术实现框架:
Spring:Spring的AOP实现较为笨重,一般用在事务处理。
aspectJ:一个开源,专门做AOP的框架,隶属于Eclipse基金会。
Spring框架集成了aspectJ的功能。
aspectJ框架实现AOP有两种方式:
1)使用xml配置文件,一般用于配置全局事务;
2)使用注解。一般在项目开发中使用这种方式。
AspectJ 对 AOP 的实现
AspectJ 的通知类型
切面的执行时间, 这个执行时间在规范中叫做Advice(通知,增强)
AspectJ 中常用的通知有五种类型,体现在五个不同的添加在切面的注解:
(1)前置通知 @Before
(2)后置通知 @AfterReturning
(3)环绕通知 @Around
(4)异常通知 @AfterThrowing
(5)最终通知 @After
AspectJ 的切入点表达式
表示切面执行的位置,使用的是切入点表达式
AspectJ 定义了专门的表达式用于指定切入点。表达式的原型是:
execution ( [modifiers-pattern] 访问权限类型ret-type-pattern 返回值类型[declaring-type-pattern] 全限定性类名 name-pattern(param-pattern) 方法名(参数类型和参数个数) [throws-pattern] 抛出异常类型 )
切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就是方法的签名
。注意,表达式中加[ ]的部分表示可省略部分,各部分间用空格分开。
在其中可以使用以下符号:
*
:0至多个任意字符
..
:用在方法参数中,表示任意多个参数;用在包名后,表示当前包及其子包
+
:用在类名后,表示当前类及其子类;用在接口名后,表示当前接口及其实现类
例如:
execution(* *..service.*.*(..))
上面表达的意思是返回值任意, 指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点
技巧:
execution(访问权限 方法返回值 方法声明(参数) 异常类型) 方法返回值和方法声明是必需的。
其实类似于声明一个方法: public void 方法名(参数) throws 异常
AspectJ 的开发环境
(1)引入AspectJ依赖:
<!--aspectj的依赖-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.2.4.RELEASE</version>
</dependency>
(2)引入约束(第4、7、8行)
<?xml version="1.0" encoding="UTF-8"?>
<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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"></beans>
在IDEA中开发,可以省略这一步。在添加aop标签时会自动引入约束文件。
AspectJ 基于注解的 AOP 实现
AspectJ 基于注解的 AOP 实现步骤
(1)定义业务接口与实现类
package com.kwxy.service;public interface SomeService {void doSome(String name, int age);
}
package com.kwxy.service.impl;import com.kwxy.service.SomeService;public class SomeServiceImpl implements SomeService {@Overridepublic void doSome(String name, int age) {System.out.println("SomeSeviceImpl的业务方法doSome");}
}
(2)定义切面类
package com.kwxy.aspect;import org.aspectj.lang.annotation.Before;import java.util.Date;@Aspect
public class MyAspect {/*** @Before: 前置通知注解* 属性:value ,是切入点表达式,表示切面的功能执行的位置。* 位置:在方法的上面* 特点:* 1.在目标方法之前先执行的* 2.不会改变目标方法的执行结果* 3.不会影响目标方法的执行。*/@Before(value = "execution(public void com.kwxy.service.impl.SomeServiceImpl.doSome(..))")public void myBefore(){//就是你切面要执行的功能代码System.out.println("前置通知, 切面功能:在目标方法之前输出执行时间:"+ new Date());}
}
(3)声明目标对象与切面类对象,注册AspectJ的自动代理
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"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/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"><!--声明目标对象--><bean id="someService" class="com.kwxy.service.impl.SomeServiceImpl"/><!--声明切面类对象--><bean id="myAspect" class="com.kwxy.aspect.MyAspect"/><!--声明自动代理生成器:使用aspectj框架内部的功能,创建目标对象的代理对象。创建代理对象是在内存中实现的, 修改目标对象的内存中的结构。 创建为代理对象所以目标对象就是被修改后的代理对象.aspectj-autoproxy:会把spring容器中的所有的目标对象,一次性都生成代理对象。--><aop:aspectj-autoproxy/>
</beans>
<aop:aspectj-autoproxy/>
通过扫描找到@Aspect 定义的切面类,再由切面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点。
(4)创建测试方法测试,获取代理对象根据目标对象的id。
@Test
public void test01(){//定义Spring的配置文件, 配置文件是在类路径的根目录之下String config = "applicationContext.xml";ApplicationContext ac = new ClassPathXmlApplicationContext(config);//从容器中获取对象 使用getBean("<bean>的id")SomeService proxy = (SomeService) ac.getBean("someService");System.out.println(proxy.getClass().getName());//调用业务方法proxy.doSome("李晶",23);
}
/*输出结果
com.sun.proxy.$Proxy8
前置通知, 切面功能:在目标方法之前输出执行时间:Wed Jul 22 19:33:30 CST 2020
SomeSeviceImpl的业务方法doSome
*/
AspectJ 通知注解
通知注解:
在切面类中修饰方法的注解,这些注解体现了通知类型。例如上面例子中@Before就是一个通知注解。通知注解修饰的方法称之为通知方法。
一共有五种通知类型,就对应了五种通知注解。下面一一介绍。
@Before前置通知-方法有JoinPoint参数
在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个 JoinPoint 类型参 数。该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、 目标对象等。
@Before(value = "execution(public void com.kwxy.service.impl.SomeServiceImpl.doSome(..))")public void myBefore(JoinPoint jp) {//在方法中,实现功能的增强,例如日志的代码//获取方法的定义System.out.println("连接点的方法定义:" + jp.getSignature());System.out.println("连接点的方法名称:" + jp.getSignature().getName());//获取方法执行时的参数Object args[] = jp.getArgs();for (Object arg : args) {System.out.println(arg);}}/*
测试方法输出结果:
com.sun.proxy.$Proxy8
连接点的方法定义:void com.kwxy.service.SomeService.doSome(String,int)
连接点的方法名称:doSome
李晶
23
*/
不光前置通知的方法,可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该参数。
@AfterReturning 后置通知-注解有 returning 属性
在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值。该注解的 returning 属性就是用于指定接收方法返回值的变量名的。
所以,被注解为后 置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。该变 量最好为 Object 类型,因为目标方法的返回值可能是任何类型。
例如:在业务接口中定义一个有返回值的抽象方法:
String doOther(String name,int age);
再在业务类中实现:
@Override
public String doOther(String name, int age) {System.out.println("SomeSeviceImpl的业务方法doOther");return "abcd";
}
然后在切面类中定义一个切面:
@AfterReturning(value = "execution(public String com.kwxy.service.impl.SomeServiceImpl.doOther(..))", returning = "result")
public void myAfterReturning(JoinPoint jp, Object result) {//修改目标方法的返回值if (result != null) {String st = (String) result;result = st.toUpperCase();}System.out.println("后置通知,在目标方法之后执行的。能够获取到目标方法的执行结果:" + result);
}
测试方法:
@Test
public void test03(){//定义Spring的配置文件, 配置文件是在类路径的根目录之下String config = "applicationContext.xml";ApplicationContext ac = new ClassPathXmlApplicationContext(config);//从容器中获取对象 使用getBean("<bean>的id")SomeService proxy = (SomeService) ac.getBean("someService");System.out.println(proxy.getClass().getName());//调用业务方法String res = proxy.doOther("李晶", 23);System.out.println("测试方法中的结果:" + res);
}/*
测试方法输出结果:
com.sun.proxy.$Proxy8
SomeSeviceImpl的业务方法doOther
后置通知,在目标方法之后执行的。能够获取到目标方法的执行结果:ABCD
测试方法中的结果:abcd
*/
@Around 环绕通知-增强方法有 ProceedingJoinPoint 参数
在目标方法执行之前之后执行。
被注解为环绕增强的方法要有返回,Object 类型。并 且方法可以包含一个 ProceedingJoinPoint 类型的参数。
接口 ProceedingJoinPoint 继承于JoinPoint,因此可以根据它获取方法的信息。
其有一个 proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。
最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。
接口增加方法:
String doFirst(String name,int age);
在实现类中实现该方法:
@Override
public String doFirst(String name, int age) {System.out.println("SomeSeviceImpl的业务方法doFirst");return "doFirst";
}
增加切面:
@Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")public Object myAround(ProceedingJoinPoint pjp) throws Throwable {//ProceedingJoinPoint能获取连接点方法的定义,参数等信息String name = "";Object args[] = pjp.getArgs();if (args.length > 1) {name = (String) args[0];}Object result = null;System.out.println("环绕通知:在目标方法之前加入日志");//控制目标方法是否执行if ("zs".equals(name)) {//执行目标方法result = pjp.proceed(); //doFirst result = method.invoke(target, args);}System.out.println("环绕通知:在目标方法之后加入事务处理");//返回目标方法的执行结果(可以是修改后的结果)//修改目标方法的执行结果if (result != null) {result = "Hello AspectJ";}return result;}
环绕通知能够控制目标方法是否执行,创建测试方法:
在上述代码中,如果name是zs,则执行,否则不执行。
@Test
public void test03(){//定义Spring的配置文件, 配置文件是在类路径的根目录之下String config = "applicationContext.xml";ApplicationContext ac = new ClassPathXmlApplicationContext(config);//从容器中获取对象 使用getBean("<bean>的id")SomeService proxy = (SomeService) ac.getBean("someService");System.out.println(proxy.getClass().getName());//调用业务方法String res = proxy.doFirst("李晶", 23);System.out.println("测试方法中的结果:" + res);
}
/*
测试结果:
com.sun.proxy.$Proxy8
环绕通知:在目标方法之前加入日志
环绕通知:在目标方法之后加入事务处理
测试方法中的结果:null
*/
将name改为zs后,运行结果为:
/*
com.sun.proxy.$Proxy8
环绕通知:在目标方法之前加入日志
SomeSeviceImpl的业务方法doFirst
环绕通知:在目标方法之后加入事务处理
测试方法中的结果:Hello AspectJ
*/
@AfterThrowing 异常通知-注解中有 throwing 属性
在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。
当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象。
在效果上相当于一个try…catch语句。目标方法的方法体在try语句块中,而切面方法的方法体放在了catch子句中。
@After最终通知
无论目标方法是否抛出异常,该增强均会被执行。
在执行效果上,相当于将切面方法的方法体放在了try…catch…finally…语句的finally子句中。
@Pointcut 定义切入点
当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。 AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。
其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性值均 可使用该方法名作为切入点。
代表的就是@Pointcut 定义的切入点。这个使用@Pointcut 注解的方法一般使用 private 的标识方法,即没有实际作用的方法。 方法体内部也无需添加代码。
例如:
@After(value = "mypt()")public void myAfter(){System.out.println("最终通知,总是会被执行的,可以做程序最后要做的工作,例如资源回收,内存释放");}/*** @Pointcut: 定义和管理切入点* 属性:value 切入点表达式* 位置:在自定义的方法上面。* 作用:@Pointcut定义在方法的上面, 这个方法的名称就是切入点的别名* 其他的通知注解的value属性可以使用方法名称,表示切入点。*/@Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))")private void mypt(){//无需代码}
设置AspectJ 实现 AOP的方式
在Spring配置文件中,通过<aop:aspectj-autoproxy/>
的proxy-target-class属性设置选择通过JDK动态代理还是cglib动态代理实现AOP。
<!--声明自动代理生成器:使用aspectj把spring容器中目标类对象生成代理proxy-target-class="true"表示使用cglib动态代理目标类有接口,默认使用jdk动态代理。目标类没有接口,默认时候cglib动态代理目标类有接口,也可以使用cglib动态代理,需要设置proxy-target-class="true"
--><!-- <aop:aspectj-autoproxy proxy-target-class="true" />--><aop:aspectj-autoproxy/>
Spring集成myBatis
概述
Spring集成myBatis,其本质工作就是:将使用mybatis框架时用到的一些需要自己创建的对象,交由Spring统一管理。
把mybatis框架和spring集成在一起,像一个框架一样使用。
用的技术是:ioc 。
为什么是ioc:能把mybatis和spring集成在一起,像一个框架, 是因为ioc能创建对象。
可以把mybatis框架中的对象交给spring统一创建, 开发人员从spring中获取对象。
开发人员就不用同时面对两个或多个框架了, 就面对一个spring。
说明:
/*
mybatis使用步骤,对象
1.定义dao接口 ,StudentDao
2.定义mapper文件 StudentDao.xml
3.定义mybatis的主配置文件 mybatis-config.xml
4.创建dao的代理对象, StudentDao dao = SqlSession.getMapper(StudentDao.class);List<Student> students = dao.selectStudents();要使用dao对象,需要使用getMapper()方法。
怎么能使用getMapper()方法,需要哪些条件?
1.获取SqlSession对象, 需要使用SqlSessionFactory的openSession()方法。
2.创建SqlSessionFactory对象。 通过读取mybatis的主配置文件,能创建SqlSessionFactory对象需要SqlSessionFactory对象, 使用Factory能获取SqlSession ,有了SqlSession就能有dao , 目的就是获取dao对象
Factory创建需要读取主配置文件我们会使用独立的连接池类替换mybatis默认自己带的, 把连接池类也交给spring创建。主配置文件:1.数据库信息<environment id="mydev"><transactionManager type="JDBC"/><dataSource type="POOLED"><!--数据库的驱动类名--><property name="driver" value="com.mysql.jdbc.Driver"/><!--连接数据库的url字符串--><property name="url" value="jdbc:mysql://localhost:3306/ssm"/><!--访问数据库的用户名--><property name="username" value="root"/><!--密码--><property name="password" value="root"/></dataSource>
2. mapper文件的位置<mappers><mapper resource="com/kwxy/dao/StudentDao.xml"/></mappers>
*/
通过以上的说明,我们需要让spring创建以下对象:
(1)数据源dataSource。就是保存数据库连接信息的对象。在实际业务开发中,我们放弃使用Mybatis自带的数据库连接池,而采用阿里的Druid,更加高效;
(2)生成sqlSession对象的sqlSessionFactory;
(3)Dao接口的实现类对象。
需要学习就是上面三个对象的创建语法,使用xml的bean标签。
Spring集成myBatis创建项目的流程
(1)新建 mysql数据库,准备数据。( student表)
建表语句
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(80) DEFAULT NULL,`email` varchar(100) DEFAULT NULL,`age` int(11) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1003 DEFAULT CHARSET=utf8;
添加数据
INSERT INTO `student` VALUES ('1001', '李晶', 'lijing@163.com', '23');
INSERT INTO `student` VALUES ('1002', '李慧', 'lihui@qq.com', '25');
mysql> select *from student;
+------+--------+----------------+------+
| id | name | email | age |
+------+--------+----------------+------+
| 1001 | 李晶 | lijing@163.com | 23 |
| 1002 | 李慧 | lihui@qq.com | 25 |
+------+--------+----------------+------+
(2)新建maven的module
(3)加入依赖
1)spring依赖
2)mybatis的依赖
3)mybatis-spring依赖, 这个jar是从mybatis官网下载的, mybatis提供在spring中创建对象的类。
4)mysql的驱动
5)druid,数据库连接池的依赖
<?xml version="1.0" encoding="UTF-8"?><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.0</modelVersion><groupId>com.kwyx</groupId><artifactId>spring_mybatis</artifactId><version>1.0-SNAPSHOT</version><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><dependencies><!--单元测试--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version><scope>test</scope></dependency><!--Spring的依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.4.RELEASE</version></dependency><!--spring的事务--><dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId><version>5.2.4.RELEASE</version></dependency><!--spring访问数据库--><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.2.4.RELEASE</version></dependency><!--aspectj的依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.2.4.RELEASE</version></dependency><!--mybatis依赖--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.1</version></dependency><!--mybatis整合spring的依赖:创建mybatis对象--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>1.3.1</version></dependency><!--mysql驱动--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.9</version></dependency><!--数据库连接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.12</version></dependency></dependencies><build><resources><resource><directory>src/main/java</directory><!--所在的目录--><includes><!--包括目录下的.properties,.xml 文件都会扫描到--><include>**/*.properties</include><include>**/*.xml</include></includes><filtering>false</filtering></resource></resources></build>
</project>
(4)新建实体类Student
package com.kwyx.domain;public class Student {private Integer id;private String name;private String email;private Integer age;public Student() {}public Student(Integer id, String name, String email, Integer age) {this.id = id;this.name = name;this.email = email;this.age = age;}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getEmail() {return email;}public void setEmail(String email) {this.email = email;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}@Overridepublic String toString() {return "Student{" +"id=" + id +", name='" + name + '\'' +", email='" + email + '\'' +", age=" + age +'}';}
}
记得在pom.xml中添加资源插件!
(5)新建Dao接口和sql映射文件
package com.kwyx.dao;import com.kwyx.domain.Student;import java.util.List;public interface StudentDao {List<Student> selectAll();int insertStudent(Student student);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kwxy.dao.StudentDao"><select id="selectAll" resultType="com.kwxy.domain.Student">select id,name,email,age from student</select><insert id="insertStudent">insert into student values (#{id},#{name},#{email},#{age})</insert>
</mapper>
(6)新建mybatis主配置文件mybatis-config.xml
由于使用阿里的数据库连接池,所以不需要<environments/>
标签
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><!--settings:控制mybatis全局行为--><settings><!--设置mybatis输出日志--><setting name="logImpl" value="STDOUT_LOGGING"/></settings><!--设置别名--><typeAliases><!--name:实体类所在的包名--><package name="com.kwxy.domain"/></typeAliases><!-- sql mapper(sql映射文件)的位置--><mappers><!--name:是包名, 这个包中的所有mapper.xml一次都能加载--><package name="com.kwxy.dao"/></mappers>
</configuration>
(7)新建Service接口和实现类, 在实现类中有Dao的属性
在实际项目中,我们在对数据库前需要一些其他的业务代码,例如逻辑判断、身份认证等,这些放在Service中
package com.kwyx.service;import com.kwyx.domain.Student;import java.util.List;public interface StudentService {List<Student> selectAll();int insertStudent(Student student);
}
package com.kwyx.service.impl;import com.kwyx.service.StudentService;
import com.kwyx.dao.StudentDao;
import com.kwyx.domain.Student;import java.util.List;public class StudentServiceImpl implements StudentService {private StudentDao studentDao;//使用ioc,设值注入,在配置文件中给dao赋值public void setStudentDao(StudentDao studentDao) {this.studentDao = studentDao;}@Overridepublic List<Student> selectAll() {return studentDao.selectAll();}@Overridepublic int insertStudent(Student student) {return studentDao.insertStudent(student);}
}
(8)创建数据库连接配置文件jdbc.properties
jdbc.url=jdbc:mysql://localhost:3306/ssm
jdbc.user=root
jdbc.password=root
jdbc.maxActive=20
不需要配置数据库驱动
(9)新建Spring的配置文件applicationContext.xml(重要)
1)声明Druid的数据源DruidDataSource对象
2)声明SqlSessionFactoryBean,创建SqlSessionFactory对象
3)声明MyBatis的扫描器MapperScannerConfigurer,创建Dao接口的实现类对象
4)声明自定义的Service ,把3)中的Dao对象注入赋值给Service的属性
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"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 https://www.springframework.org/schema/context/spring-context.xsd"><!--读取jdbc配置文件location:指定属性配置文件的路径"classpath:":关键字表示类文件,也就是class文件所在的目录--><context:property-placeholder location="classpath:jdbc.properties"/><!--声明数据源DataSource--><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><!--读取属性配置文件的key的值,使用 ${key}--><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.user}"/><property name="password" value="${jdbc.password}"/><property name="maxActive" value="${jdbc.maxActive}"/></bean><!--DruidDataSource myDataSource = new DruidDataSource();myDataSource.setUrl();myDataSource.setUsername();myDataSource.setPassword();myDataSource.init();--><!--声明SqlSessionFactoryBean,创建SqlSessionFactory对象--><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><!--数据源--><property name="dataSource" ref="dataSource"/><!--指定mybatis的主配置文件--><property name="configLocation" value="classpath:mybatis-config.xml"/></bean><!--声明MyBatis的扫描器,创建Dao接口的实现类对象--><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><!--指定SqlSessionFactory对象,能获取SqlSession--><property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/><!--指定Dao接口的包名,框架会把这个包中的所有接口一次创建出Dao对象--><property name="basePackage" value="com.kwxy.dao"/></bean><!--从spring中获取SqlSessionFacotory,因为spring是一个容器(Map)SqlSessionFactory factory = map.get("sqlSessionFactory");SqlSession session = factory.openSession();for(接口:com.kwxy.dao){Dao对象 = session.getMapper(接口)//把创建好的对象放入到spring容器中spring的Map.put( 接口名的首字母小写, Dao对象 )}创建好的dao对象放入到spring的容器中的。 dao对象的默认名称是 接口名首字母小写--><!--声明Service--><bean id="studentService" class="com.kwxy.service.impl.StudentServiceImpl"><property name="studentDao" ref="studentDao"/></bean>
</beans>
(10)新建测试类, 从spring容器中获取Service,调用Service的方法,完成数据库的操作
public class MyTest {@Testpublic void test01() {ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");StudentService studentService = (StudentService) ac.getBean("studentService");List<Student> students = studentService.selectAll();for (Student student : students) {System.out.println("学生 :" + student);}}/**结果* JDBC Connection [com.mysql.jdbc.JDBC4Connection@662b4c69] will not be managed by Spring* ==> Preparing: select id,name,email,age from student* ==> Parameters:* <== Columns: id, name, email, age* <== Row: 1001, 李晶, lijing@163.com, 23* <== Row: 1002, 李慧, lihui@qq.com, 25* <== Total: 2* Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@53aad5d5]* 学生 :Student{id=1001, name='李晶', email='lijing@163.com', age=23}* 学生 :Student{id=1002, name='李慧', email='lihui@qq.com', age=25}*/@Testpublic void test02() {ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");StudentService studentService = (StudentService) ac.getBean("studentService");Student student = new Student(3, "张飞", "zhangfei@163.com", 24);int nums = studentService.insertStudent(student);System.out.println("添加了 " + nums + " 位学生");}/**结果* JDBC Connection [com.mysql.jdbc.JDBC4Connection@662b4c69] will not be managed by Spring* ==> Preparing: insert into student values (?,?,?,?)* ==> Parameters: 3(Integer), 张飞(String), zhangfei@163.com(String), 24(Integer)* <== Updates: 1* Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@76f2bbc1]* 添加了 1 位学生*/
}
Spring与事务
理论知识
(1)什么是事务
讲mysql的时候,提出了事务。 事务是指一组sql语句的集合, 集合中有多条sql语句,
可能是insert , update ,select ,delete, 我们希望这些多个sql语句都能成功,
或者都失败, 这些sql语句的执行是一致的,作为一个整体执行。
(2)在什么时候想到使用事务
当我的操作,涉及得到多个表,或者是多个sql语句的insert,update,delete。
需要保证这些语句都是成功才能完成我的功能,或者都失败,保证操作是符合要求的。
在java代码中写程序,控制事务,此时事务应该放在哪里呢?
service类的业务方法上,因为业务方法会调用多个dao方法,执行多个sql语句。
(3)通常使用JDBC访问数据库, 还是mybatis访问数据库怎么处理事务
jdbc访问数据库,处理事务 Connection conn ; conn.commit(); conn.rollback();
mybatis访问数据库,处理事务, SqlSession.commit(); SqlSession.rollback();
hibernate访问数据库,处理事务, Session.commit(); Session.rollback();
(4)上面事务的处理方式,有什么不足
不同的数据库访问技术,处理事务的对象,方法不同,需要了解不同数据库访问技术使用事务的原理
要掌握多种数据库中事务的处理逻辑,什么时候提交事务,什么时候回顾事务
处理事务需要多种方法
(5)怎么解决不足
spring提供一种处理事务的统一模型, 能使用统一步骤,方式完成多种不同数据库访问技术的事务处理。
使用spring的事务处理机制,可以完成mybatis访问数据库的事务处理。
使用spring的事务处理机制,可以完成hibernate访问数据库的事务处理。
spring处理事务的模型,使用的步骤都是固定的。把事务使用的信息提供给spring就可以了。
事务管理器接口
事务管理器是 PlatformTransactionManager 接口对象。其主要用于完成事务的提交、回滚,及获取事务的状态信息。
PlatformTransactionManager 接口有两个常用的实现类:
➢ DataSourceTransactionManager:使用 JDBC 或 MyBatis 进行数据库操作时使用。
➢ HibernateTransactionManager:使用 Hibernate 进行持久化数据时使用。
Spring 的回滚方式
Spring 事务的默认回滚方式是:发生运行时异常和 error 时回滚,发生受查(编译)异常时提交。不过,对于受查异常,程序员也可以手工设置其回滚方式。
运行时异常
,是 RuntimeException 类或其子类,即只有在运行时才出现的异常。
如, NullPointerException、ArrayIndexOutOfBoundsException、IllegalArgumentException 等均属于运 行时异常。
这些异常由 JVM 抛出,在编译时不要求必须处理(捕获或抛出)。
但,只要代码 编写足够仔细,程序足够健壮,运行时异常是可以避免的。
受查异常
,也叫编译时异常,即在代码编写时要求必须捕获或抛出的异常,若不处理, 则无法通过编译。
如 SQLException,ClassNotFoundException,IOException 等都属于受查异常。 RuntimeException 及其子类以外的异常,均属于受查异常。
事务定义接口
事务定义接口TransactionDefinition中定义了事务描述相关的三类常量:事务隔离级别、 事务传播行为、事务默认超时时限,及对它们的操作。
(1)定义了五个事务隔离级别常量
这些常量均是以 ISOLATION_开头。即形如 ISOLATION_XXX。
➢ DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle 默 认为 READ_COMMITTED。
➢ READ_UNCOMMITTED:读未提交。未解决任何并发问题。
➢ READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
➢ REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读 。
➢ SERIALIZABLE:串行化。不存在并发问题。
(2)定义了七个事务传播行为常量
所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情 况。如,A 事务中的方法 doSome()调用 B 事务中的方法 doOther(),在调用执行期间事务的 维护情况,就称为事务传播行为。事务传播行为是加在方法上的。 事务传播行为常量都是以 PROPAGATION_ 开头,形如 PROPAGATION_XXX。
PROPAGATION_REQUIRED
PROPAGATION_REQUIRES_NEW
PROPAGATION_SUPPORTS
PROPAGATION_MANDATORY
PROPAGATION_NESTED
PROPAGATION_NEVER
PROPAGATION_NOT_SUPPORTED
1)PROPAGATION_REQUIRED:
指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;若当前没有事 务,则创建一个新事务。这种传播行为是最常见的选择,也是 Spring 默认的事务传播行为。 如该传播行为加在 doOther()方法上。若 doSome()方法在调用 doOther()方法时就是在事 务内运行的,则 doOther()方法的执行也加入到该事务内执行。若 doSome()方法在调用 doOther()方法时没有在事务内执行,则 doOther()方法会创建一个事务,并在其中执行。
2)PROPAGATION_SUPPORTS
指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。
3)PROPAGATION_REQUIRES_NEW
总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。
使用 Spring 的事务注解管理事务
(1)开启注解驱动,告诉Spring框架现在使用注解处理事务,在spring配置文件applicationContext中添加如下代码:
<!--开启注解驱动,开启事务支持,以tx开头,可以看出是用于事务的-->
<tx:annotation-driven transaction-manager="transactionManager"/>
(2)声明事务管理器
<!--声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/>
</bean>
可视为固定写法,其中property标签的ref是配置文件中数据源对象的id属性值。
(3)业务层 public 方法加入事务注解
/**** rollbackFor:表示发生指定的异常一定回滚.* 处理逻辑是:* 1) spring框架会首先检查方法抛出的异常是不是在rollbackFor的属性值中* 如果异常在rollbackFor列表中,不管是什么类型的异常,一定回滚。* 2) 如果你的抛出的异常不在rollbackFor列表中,spring会判断异常是不是RuntimeException,* 如果是一定回滚。**//* @Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,readOnly = false,rollbackFor = {NullPointerException.class})*///使用的是事务控制的默认值, 默认的传播行为是REQUIRED,默认的隔离级别DEFAULT//默认抛出运行时异常,回滚事务。@Transactional@Overridepublic int insertStudent(Student student) {return studentDao.insertStudent(student);}
@Transactional 的所有可选属性
如下所示:
➢ propagation:用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值为Propagation.REQUIRED。
➢ isolation:用于设置事务的隔离级别。该属性类型为 Isolation 枚举,默认值为 Isolation.DEFAULT。
➢ readOnly:用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值 为 false。
➢ timeout:用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为 -1,即没有时限。在实际业务开发中一般不设置
。
➢ rollbackFor:指定需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有 一个异常类时,可以不使用数组。
➢ rollbackForClassName:指定需要回滚的异常类类名。类型为 String[],默认值为空数组。 当然,若只有一个异常类时,可以不使用数组。
➢ noRollbackFor:指定不需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若 只有一个异常类时,可以不使用数组。
➢ noRollbackForClassName:指定不需要回滚的异常类类名。类型为 String[],默认值为空 数组。当然,若只有一个异常类时,可以不使用数组。
需要注意的是,@Transactional 若用在方法上,只能用于 public 方法上。
对于其他非 public 方法,如果加上了注解@Transactional,虽然 Spring 不会报错,
但不会将指定事务织入到该方法中。因为 Spring 会忽略掉所有非 public 方法上的@Transaction注解。
若@Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务。
使用 AspectJ 的 AOP 配置管理事务
一般大型项目使用。在不更改源代码的条件下管理事务。代码和事务的配置完全是分离的,不需要在代码上加注解,全部是在xml中配置。
(1)添加Maven依赖
<!--aspectj的依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.2.4.RELEASE</version></dependency>
(2)声明事务管理器
<!--声明事务管理器对象-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/>
</bean>
(3)配置事务通知
为事务通知设置相关属性。用于指定要将事务以什么方式织入给哪些方法。
<!--声明业务方法它的事务属性(隔离级别,传播行为,超时时间)id:自定义名称,表示 <tx:advice> 和 </tx:advice>之间的配置内容的transaction-manager:事务管理器对象的id--><tx:advice id="buyAdvice" transaction-manager="transactionManager"><tx:attributes><!--tx:method:给具体的方法配置事务属性,method可以有多个,分别给不同的方法设置事务属性name:方法名称,1)完整的方法名称,不带有包和类。2)方法可以使用通配符,* 表示任意字符propagation:传播行为,枚举值isolation:隔离级别rollback-for:你指定的异常类名,全限定类名。 发生异常一定回滚--><tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"rollback-for="java.lang.NullPointerException"/><!--使用通配符,指定很多的方法--><tx:method name="add*" propagation="REQUIRES_NEW" /><!--指定修改方法--><tx:method name="modify*" /><!--删除方法--><tx:method name="remove*" /><!--查询方法,query,search,find--><tx:method name="*" propagation="SUPPORTS" read-only="true" /></tx:attributes></tx:advice>
(4)配置aop,配置增强器
指定哪些哪类要创建代理
<!--配置aop--><aop:config><!--配置切入点表达式:指定哪些包中类,要使用事务id:切入点表达式的名称,唯一值expression:切入点表达式,指定哪些类要使用事务,aspectj会创建代理对象--><aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/><!--配置增强器:关联adivce和pointcutadvice-ref:通知,上面tx:advice哪里的配置pointcut-ref:切入点表达式的id--><aop:advisor advice-ref="studentService" pointcut-ref="servicePt" /></aop:config>
Spring与Web
这一章主要介绍了一个核心知识点:解决不同Servlet中重复创建ApplicationContext对象,造成内存浪费的问题,即重复创建Spring容器。
解决这个问题的一个思路是,创建一个ServletContextListener,在ServletContext初始化的时候创建ApplicationContext对象,并将它保存在ServletContext中。
这样,在每个servlet中,只要调用当前servlet的ServletContext对象getAttribute方法就可以获取这个webapp中共享的一个ApplicationContext对象。
spring-web框架已经帮我们创建好了这样一个监听器。我们只需要在web.xml注册这个监听器就可以使用了。
(1)maven创建web模块,加入servlet,jsp依赖,拷贝之前spring和mybatis中所用的依赖
<!--以下是新增依赖,其它依赖到之前做的项目中拷贝-->
<!-- servlet依赖 -->
<dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><scope>provided</scope>
</dependency>
<!-- jsp依赖 -->
<dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.2.1-b03</version> <scope>provided</scope>
</dependency>
<!--spring-web依赖:有监听器-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>5.2.4.RELEASE</version>
</dependency>
(2)需要为监听器提供Spring的配置文件路径信息
<!--注册spring框架提供的监听器在监听器启动的时候,会寻找/WEB-INF/applicationContext.xml,为什么找这个文件?在监听器的初始方法中,会创建spring的容器对象, 在创建容器对象时,需要读取配置文件监听器默认是找/WEB-INF/applicationContext.xml。
-->
<!--自定义spring配置文件的位置和名称-->
<context-param><param-name>contextConfigLocation</param-name><param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
(3)在Sevlet中获取ApplicationContext对象
WebApplicationContext ctx = null;
String key = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
Object attr = getServletContext().getAttribute(key);
if( attr != null){ctx = (WebApplicationContext)attr;
}
webApplicationContext是ApplicationContext的子类,是在web项目中使用的Spring容器对象。
为了不使用框架给出的难记的key值获取webApplicationContext,这个框架还提供了一个工具类。
使用工具类获取webApplicationContext:
//获取ServletContext中的容器对象,spring提供了一个方法,获取容器对象
WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
Spring学习笔记相关推荐
- 【Spring学习笔记-MVC-13.2】Spring MVC之多文件上传
作者:ssslinppp 1. 摘要 前篇文章讲解了单文件上传<[Spring学习笔记-MVC-13]Spring MVC之文件上传>http://www.cnblogs.co ...
- Spring学习笔记(三) AOP_annotation,AOP_XML
在学习课程以前,听说AOP有种很神秘的感觉,好像很好深的技术.其实原理很简单,使用动态代理的方式给程序增加逻辑.与此相似的有struts2中的filter拦截器. 再讲AOP之前先把需求说一下: 同S ...
- spring学习笔记06-spring整合junit(出现的问题,解决的思路)
spring学习笔记06-spring整合junit(出现的问题,解决的思路) 文章目录 spring学习笔记06-spring整合junit(出现的问题,解决的思路) 3.1测试类中的问题和解决思路 ...
- spring学习笔记01-BeanFactory和ApplicationContext的区别
spring学习笔记01-BeanFactory和ApplicationContext的区别 BeanFactory 和 ApplicationContext 的区别 BeanFa ...
- spring学习笔记02-spring-bean创建的细节问题
spring学习笔记02-spring-bean创建的细节问题 三种创建Bean对象的方式 Bean的作用范围 Bean的生命周期 <?xml version="1.0" e ...
- spring学习笔记03-spring-DI-依赖注入详解(通过xml配置文件来配置依赖注入)
spring学习笔记03-spring-DI-依赖注入详解 1.概念 2.构造函数注入 3.set方法注入 4.集合的注入 需要被注入的实体对象 package com.itheima.service ...
- Spring学习笔记:配置单数据源
Spring学习笔记:配置单数据源 一.Spring Boot默认数据源类型 Springboot默认支持4种数据源类型,定义在 org.springframework.boot.autoconfig ...
- Spring学习笔记:第一个Spring Boot程序HelloWorld
Spring学习笔记:第一个Spring Boot程序HelloWorld 一.跟着 Spring 了解技术趋势 1.看看 Spring 5.x 的改变暗示了什么 2.Spring Boot 和 Sp ...
- Spring学习笔记之MyBatis
系列文章目录 Spring学习笔记 之 Springhttps://blog.csdn.net/weixin_43985478/article/details/124411746?spm=1001.2 ...
- 【Spring学习笔记 九】Spring声明式事务管理实现机制
什么是事务?事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用,关乎数据准确性的地方我们一定要用到事务,防止业务逻辑出错. 什么是事务管理,事务管理对于企业应用而言至 ...
最新文章
- 学习笔记(六)——JavaScript(三)
- VTK:可视化之BackgroundColor
- 关系的三类完整性约束
- 电脑如何进行长截图?
- CF probabilities 自制题单
- 小型游戏《笑傲江湖之精忠报国》全过程_01
- 蔡学镛谈Java学习
- 碎碎念No.03 我这个程序员终于去看了心理医生做了心理咨询
- virtual box linux 安装增强功能,在linux系统中安装virtualbox增强功能(增强包)的详细步骤...
- 使用MATLAB工具箱TOOLBOX_calib标定摄像头过程(双目标定)
- 浙江大学14届计算机学院孙晓宇,郑州外国语学校2011年保送生录取名单
- 高效C++ Effective C++
- 一个链表L 一个链表P 包含升序排列的整数 操作PrintLots(L,P)将打印L中那些由P所指定的位置上的元素
- Firefox流失近5000万用户,世界第三大浏览器正在消亡
- 东莞东城用“智慧大脑”给城市“打补丁”
- iOS高级面试题及部分答案
- Android开发样式问题总结【持续更新】
- 安装jupyter步骤
- 怎么把HTML文件拉出来,怎么把网页HTML格式的文件
- React Native 实现chat 即时聊天(第1天)
热门文章
- Unity 资源包导入报错 “Substance engine failed to load“
- sqlserver去空格函数
- excel中如何解锁锁定单元格
- 求四边形最大内接矩形,一种不规则多边形的最大内接矩形的快速近似求解方法与流程...
- 虚函数表和虚函数指针
- Windows 10 让所有程序默认为“以管理员身份运行”并且取消“确认”按钮
- 计算机组成原理变形补码计算
- 怎么给win10进行分区?
- 微信开发者工具封装request请求
- 1 常用邮箱SMTP/POP3地址及端口