发布时间:2023-05-19 09:00
此文章并已完结,如有错误请评论区指正。还望海涵。
目前更新到:
Spring5事务操作
所有的Jar包和相关依赖,会在文章跟新结束后,统一上传至,百度云盘。
目前已经完全书写完毕了,所对应的Jar包链接
链接:https://pan.baidu.com/s/1X85ob4gcxrelhPWRnRjb5w
提取码:0qle
1. Spring是轻量级,开源的JavaEE框架。
2. Spring可以解决企业应用开发的复杂性。
3. Spring中有两大核心部分:IOC, AOP
(1) IOC: 控制反转,把创建对象的过程交给Spring进行管理,也就是Spring帮我们创建对象。进行对象的实例化
(2) AOP: 面向切面,在不修改源代码的情况下,去进行功能的添加或者增强。
4. 关于Spring框架相关的特点:
(1) 方便解耦,简化开发
(2) AOP编程的支持
(3) 方便程序的测试
(4) 方便继承各种优秀的框架
(5) 降低JavaEE API 的使用难度
(6) 声明事务的管理
1. 要先进入Spring的官方网站, https://spring.io/
2. 找到Projects 下的 SpringFromwork
3. 然后点击LEARN 查看所有的Spring版本, 目前最新的为: 5.3.8 CURRENT GA GA表示稳定版
4. 如何下载
https://repo.spring.io/release/org/springframework/spring/ 下载对应的dist压缩包解压。
5. libs 文件夹下 有对应的jar包
Beans, Core, Context, Expression
type: spring-beans-5.3.8.jar
在根目录下新建一个 libs文件夹,导入对应的jar包
首先创建一个类
// User类
public class User {
public void add() {
sout("add........");
}
}
在spring里,注册对象有两种方式,一种 是配置文件 ,一种是注解方式,现在暂时先做入门 ,使用配置文件来书写创建对象的实例。
(1) 在spirng5里面 是使用xml文件配置的。
(2) 创建 xml文件, 为了方便,在src目录下创建,起名为 beans.xml 名字可以随便起,我习惯起名为这个
固定写法
<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>
把User类注册到Spring容器中
<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 id="user" class="com.wang.spring5.User" />
beans>
在测试类中测试是否可以获取到User类
public class MyTest {
@Test
public void MyUserTest() {
/* 个人习惯把测试类的方法名称写为测试的对象+test */
// 加载Spring 配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 取出容器中托管的对象
/* 方法getBean所传入的参数就是配置user的时候给的id参数 */
User user = context.getBean("user", User.class);
// 调用方法
user.add(); // 输出 add........
}
}
1. 什么是IOC
(1) 控制反转,把对象的创建,和对象之间的调用的过程,都交给Spring管理。
(2) 使用IOC的目的,是为了让代码和代码之间的耦合度降低。
2. IOC的底层原理
(1) XML解析技术
(2) 工厂设计模式
(3) 反射技术
public class User {
public void add() {
System.out.println("add....User");
};
}
class UserTest() {
public static void main(String[] args) {
User user = new User();
user.add(); // add....User
}
}
/* 在Service调用dao */
public class UserDao {
public void add() {
System.out.println("add...");
}
}
public class UserFactory {
public static UserDao getUserDao() {
return new UserDao();
}
}
public class UserServer {
public static void execute() {
UserDao userDao = UserFactory.getUserDao();
userDao.add();
}
public static void main(String[] args) {
execute(); // add...
}
}
最终的目的无非是最低的降低耦合度。
第一步肯定有一个创建xml文件的过程,使用Bean 配置要创建的对象。
<bean id="getBean时使用的名字" class="全类名" >bean>
<bean id="userDao" class="com.wang.UserDao">bean>
1. IOC思想基于IOC容器完成,IOC容器底层就是对象工厂。
2. Spring提供了IOC容器实现的两种方式(两个接口):
(1) BeanFactory
(2) ApplicationContext
BeanFactory是IOC容器中最基本的一种实现,是Spring内置的一种方式。一般开发中不会去使用。
ApplicationContext 是 BeanFactory接口的 一个 子接口。 这个接口中比 BeanFactory中提供了更多更强大的功能
这个接口一般是面向开发人员使用的。
ApplicationContext 下的子接口和实现类
其实指的是两个操作
(1) Spring给我们创建对象、
(2) Spring对属性的注入
Bean管理有两种实现方式:
(1) 基于XML文件方式配置
(2) 基于注解方式配置
上面已经做过无数次,不在示范。
Spring在创建对象的时候,默认会去走无参构造器。 如果没有无参构造,则会报错
(1) DI ---> 依赖注入, 就是注入属性。
DI 是 IOC 中的一种具体实现, 表示依赖注入,也就是注入属性,需要在创建对象的基础上完成。
1. id 属性
唯一标识
2. class 属性
类全路径(包类路径)
3. name 属性
已弃用
package com.wang.spring5.text;
public class Book {
private String name;
/*
有多种注入属性的方式(原始方法), 通常为 set设置属性和 有参构造。
*/
public Book() { }
public Book(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public static void main(String[] args) {
// set方式
Book book = new Book();
book.setName("《算法导论》");
System.out.println(book.getName()); // 《算法导论》
// 有参构造方式
Book book1 = new Book("《Java核心技术卷 I》");
System.out.println(book1.getName()); // 《Java核心技术卷 I》
}
}
Java代码
public class Book {
private String name;
private int price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
XML
<bean id="book" class="com.wang.spring5.text.Book">
<property name="name" value="《算法导论》" />
<property name="price" value="119" />
bean>
测试代码类
import com.wang.spring5.text.Book;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyText {
@Test
public void MyBook() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Book book = context.getBean("book", Book.class);
System.out.println(book.getName()); // 《算法导论》
System.out.println(book.getPrice()); // 119
}
}
所有的属性注入都需要在创建对象的基础上去进行
Java类
public class Orders {
// 属性
private String ordersName;
private String address;
public Orders() {
}
// 有参数的构造
public Orders(String ordersName, String address) {
this.ordersName = ordersName;
this.address = address;
}
@Override
public String toString() {
return "Orders{" +
"ordersName='" + ordersName + '\'' +
", address='" + address + '\'' +
'}';
}
}
XML
<bean id="orders" class="com.wang.spring5.text.Orders">
<constructor-arg name="ordersName" value="小熊饼干" />
<constructor-arg name="address" value="河南郑州" />
bean>
测试代码
/* 前缀太简单了。直接省略掉 自己补 */
@Test
public void MyOrders() {
ApplicationContext context =
new ClassPathXmlApplicationContext("beans.xml");
Orders orders = context.getBean("orders", Orders.class);
System.out.println(orders.toString()); // Orders{ordersName='小熊饼干', address='河南郑州'}
}
p命名空间注入,可以简化我们基于 xml 配置方式, 但是在实际中用的并不多
xmlns:p="http://www.springframework.org/schema/p"
添加p命名
案例
public class Book {
private String name;
private int price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
<bean id="book2" class="com.wang.spring5.text.Book" p:name="《Spring实战》" p:price="99" />
@Test
public void MyBook2() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Book book2 = context.getBean("book2", Book.class);
System.out.println(book2.getName()); // 《Spring实战》
System.out.println(book2.getPrice()); // 99
}
Java Book类
package com.wang.spring5.text;
public class Book {
private String name;
private String nullValue;
private int price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setNullValue(String nullValue) {
this.nullValue = nullValue;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", nullValue='" + nullValue + '\'' +
", price=" + price +
'}';
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
XML文件
<bean id="book3" class="com.wang.spring5.text.Book">
<property name="name" value="《Java核心技术卷2》" />
<property name="price" value="32" />
<property name="nullValue">
<null />
property>
bean>
Java测试类
@Test
public void MyBook3() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Book book3 = context.getBean("book3", Book.class);
System.out.println(book3.toString());
// Book{name='《Java核心技术卷2》', nullValue='null', price=32}
}
使用场景
<bean id="xx" class="xx" name="xx" value="<<我我我>>">bean>
这个时候,使用了 <<>> 会报错,因为他把小尖括号当成了你的标签括号
使用C数据
(1) 创建两个类 Service 和 Dao两个类
(2) Service 调用 DaO 类里面的方法。
首先创建两个包 Service 和 Dao
在Service下创建 UserService类, 在Dao下创建 UserDao接口,和对应的实现类
Java代码
/* UserDaoInterface */
package com.wang.spring5.Dao;
public interface UserDao {
void update();
}
/* UserDaoImpl */
package com.wang.spring5.Dao;
public class UserDaoImpl implements UserDao{
@Override
public void update() {
System.out.println("dao update.........");
}
}
/* UserService */
package com.wang.spring5.Service;
import com.wang.spring5.Dao.UserDao;
import com.wang.spring5.Dao.UserDaoImpl;
public class UserService {
public void add() {
System.out.println("service add............");
// 创建UserDao的对象
UserDao dao = new UserDaoImpl();
dao.update();
}
}
基础操作同上,包名类名 。
<bean id="service" class="com.wang.spring5.Service.UserService">
<property name="userDao" ref="userDao" />
bean>
<bean id="userDao" class="com.wang.spring5.Dao.UserDaoImpl">
bean>
Java测试类
@Test
public void serviceAndDaoSetPro() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans2.xml");
UserService service = context.getBean("service", UserService.class);
service.add();
}
使用场景
具体的一个实现内部Bean
先创建一个bean包,在包里创建一个Dept部门类。 里面有一个属性,部门名称, 书写对应的set方法和toString方法。
package com.wang.spring5.bean;
// 部门类
public class Dept {
private String DeptName;
public void setDeptName(String deptName) {
DeptName = deptName;
}
@Override
public String toString() {
return "Dept{" +
"DeptName='" + DeptName + '\'' +
'}';
}
}
创建员工类,Emp 有两个属性, 员工姓名和员工性别,书写对应的set方法,和toString方法;
员工属于某一个部门,所以有一个属性是 Dept dept;
package com.wang.spring5.bean;
public class Emp {
private String EmpName;
private String gender;
// 员工属于某一个部门 使用对象表示
private Dept dept;
public void setDept(Dept dept) {
this.dept = dept;
}
public void setEmpName(String empName) {
EmpName = empName;
}
public void setGender(String gender) {
this.gender = gender;
}
@Override
public String toString() {
return "Emp{" +
"EmpName='" + EmpName + '\'' +
", gender='" + gender + '\'' +
", dept=" + dept +
'}';
}
}
xml的书写方式
<bean id="emp" class="com.wang.spring5.bean.Emp">
<property name="empName" value="张三" />
<property name="gender" value="男" />
<property name="dept">
<bean class="com.wang.spring5.bean.Dept">
<property name="deptName" value="安保部" />
bean>
property>
bean>
Java测试类
public class MyTest3 {
@Test
public void Test1() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans3.xml");
Emp emp = context.getBean("emp", Emp.class);
System.out.println(emp.toString());
// Emp{EmpName='张三', gender='男', dept=Dept{DeptName='安保部'}}
}
}
第一种写法
类依然是上面呢两个类, Emp和Dept 类,改变的只有Xml和 Test类
<bean id="emp2" class="com.wang.spring5.bean.Emp">
<property name="empName" value="张三" />
<property name="gender" value="男" />
<property name="dept" ref="dept">
property>
bean>
<bean id="dept" class="com.wang.spring5.bean.Dept">
<property name="deptName" value="财务部" />
bean>
实则不然,其实test类改变的也只有 获取配置文件的文件名称和getBean方法的参数改变。
@Test
public void Test1() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans4.xml");
Emp emp = context.getBean("emp2", Emp.class);
System.out.println(emp.toString());
// Emp{EmpName='张三', gender='男', dept=Dept{DeptName='安保部'}}
}
一个通用的Java类
package com.w.spring5.collectiontype;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class Student {
// 1. 数组类型的属性
private String[] courses;
// 2.list集合属性
private List<String > list;
// 3. Map集合类型
private Map<String, String > maps;
// 4. Set集合类型
private Set<String > strings;
public void setCourses(String[] courses) {
this.courses = courses;
}
public void setList(List<String> list) {
this.list = list;
}
public void setMaps(Map<String, String> maps) {
this.maps = maps;
}
public void setStrings(Set<String> strings) {
this.strings = strings;
}
@Override
public String toString() {
return "Student{" +
"courses=" + Arrays.toString(courses) +
", list=" + list +
", maps=" + maps +
", strings=" + strings +
'}';
}
}
注入数组
<property name="courses">
<array>
<value>语文value>
<value>数学value>
<value>英语value>
<value>地理value>
array>
property>
<property name="list">
<list>
<value>110value>
<value>99value>
<value>89value>
<value>101value>
list>
property>
<property name="maps">
<map>
<entry key="张" value="三" />
<entry key="李" value="四" />
<entry key="王" value="五" />
<entry key="秦" value="六" />
map>
property>
<property name="strings">
<set>
<value>set1value>
<value>set2value>
<value>set3value>
<value>set4value>
set>
property>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="student" class="com.w.spring5.collectiontype.Student">
<property name="courses">
<array>
<value>语文value>
<value>数学value>
<value>英语value>
<value>地理value>
array>
property>
<property name="list">
<list>
<value>110value>
<value>99value>
<value>89value>
<value>101value>
list>
property>
<property name="maps">
<map>
<entry key="张" value="三" />
<entry key="李" value="四" />
<entry key="王" value="五" />
<entry key="秦" value="六" />
map>
property>
<property name="strings">
<set>
<value>set1value>
<value>set2value>
<value>set3value>
<value>set4value>
set>
property>
bean>
beans>
<property name="coursesList">
<list>
<ref bean="course1" />
<ref bean="course2" />
list>
property>
bean>
<bean id="course1" class="com.w.spring5.collectiontype.Course">
<property name="courseName" value="Spring" />
bean>
<bean id="course2" class="com.w.spring5.collectiontype.Course">
<property name="courseName" value="MyBatis" />
bean>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation=
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<util:list id="bookList">
<value>《Java核心技术卷 I》value>
<value>《Java核心技术卷 II》value>
<value>《Java编程思想》value>
util:list>
<bean id="book" class="com.w.spring5.collectiontype.Book">
<property name="list" ref="bookList" />
bean>
beans>
/* Java测试代码 */
package com.w.spring5.test;
import com.w.spring5.collectiontype.Book;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void main() {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("beans2.xml");
Book book = applicationContext.getBean("book", Book.class);
System.out.println(book.toString());
// Book{list=[《Java核心技术卷 I》, 《Java核心技术卷 II》, 《Java编程思想》]}
}
}
1. Spring有两种类型的Bean,一种普通的Bean,另一种工厂Bean(FactoryBean)
他们之间有不同的区别,这是一个重要的概念。
普通Bean
在Spring配置文件,你定义的类型,就是返回的类型。
工厂Bean
在配置文件中,定义的类型,可以和你返回的类型不一样。
在Java该类继承了 FactoryBean 接口之后, 需要实现三个方法,其中getObject 方法, 是返回类的值。
所以,可以在实现接口的时候,给予对应的泛型,即可实现工厂类,最后return出去,在XML文件中创建,
而在Java测试代码中,接收getBean(“myBean”)的数据类型,应该是对应的返回类型,或者大类Object。
package com.w.spring5.factorybean;
import com.w.spring5.collectiontype.Course;
import org.springframework.beans.factory.FactoryBean;
public class MyBean implements FactoryBean<Course> {
@Override
public boolean isSingleton() {
return FactoryBean.super.isSingleton();
}
/*
* 定义返回Bean
* 表示,我设置的MyBean对象,返回的并不是自己本身,
* 而是我在getObject里返回的对象。Course
* */
@Override
public Course getObject() throws Exception {
Course course = new Course();
course.setCourseName("Java课程");
return course;
}
@Override
public Class<?> getObjectType() {
return null;
}
}
<bean id="myBean" class="com.w.spring5.factorybean.MyBean">
bean>
/* 测试 */
@Test
public void main2() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans3.xml");
Object myBean = context.getBean("myBean");
System.out.println(myBean);
// Course{courseName='Java课程'}
}
Spring里面,创建创建Bean实例是单实例还是多实例。
在Spring里面,在默认情况下,创建的Bean是一个单实例对象。
在spring的配置文件Bean标签里,有一个属性 用于设置是单实例还是多实例
<bean scope="">bean>
第一个值,默认值, singleton,表示单实例对象
第二个值,prototype,表示多实例对象。
第三个第四个不常用
request
表示一次请求
session
表示一次会话
生命周期:
一个对象,从你对象创建,到对象销毁的一个过程。
Bean的生命周期:
(1) 通过构造器去创建Bean的实例(无参数的构造)。
(2) 如果 Bean 中有属性,就去设置他对应的值。 或者对Bean的一些引用(调用set方法)
(3) 调用Bean的初始化方法。(需要进行配置)
(4) Bean可以使用(对象获取到了)
(5) 当容器关闭的时候,调用Bean销毁的方法。(销毁, 需要自己进行配置销毁。)
首先创建一个包,命名为bean,然后在包里创建一个类,命名为 Orders 订单类。
package com.w.spring5.bean;
public class Orders {
private String ordersName;
public void setOrdersName(String ordersName) {
this.ordersName = ordersName;
}
@Override
public String toString() {
return "Orders{" +
"ordersName='" + ordersName + '\'' +
'}';
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation=
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<bean id="orders" class="com.w.spring5.bean.Orders">
<property name="ordersName" value="手机" />
bean>
beans>
第一步是要走类内部的无参构造,为了明显,我们需要在Orders类里添加无参构造
public Orders() {
System.out.println("第一步, 执行无参构造创建Bean实例");
}
第二步是要调用set方法来进行属性注入,所以我们需要在对应的set方法输出一句话,来证实。
public void setOrdersName(String ordersName) {
this.ordersName = ordersName;
System.out.println("第二部, 调用set方法,进行属性注入");
}
第三步,执行默认初始化方法。我们并没有在Java类中对初始化方法进行书写,所以进行书写初始化方法。
这里命名为 initMethod 名称是随便起的,翻译过来就是初始化方法的意思。
// 创建执行的初始化方法
public void initMethod() {
System.out.println("第三步, 执行初始化的方法。");
}
但是,这里无疑是 一个普通的方法,并不是初始化方法,所以我们需要对该方法进行配置。 在Xml里有一个属性
叫 init-method 对应的值为, 你类里想要的初始化方法的方法名称。
<bean id="orders" class="com.w.spring5.bean.Orders" init-method="initMethod">
<property name="ordersName" value="手机" />
bean>
第四步,获取一个对象。这是最常用到的,也就是ApplicationContext 接口来获取对应的 Bean对象。
@Test
public void ordersTest() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans4.xml");
Orders orders = context.getBean("orders", Orders.class);
System.out.println("第四步,获取Bean容器并且使用");
System.out.println(orders.toString());
}
第五步,销毁。 在销毁的时候,会执行一个销毁的方法,这个方法需要我们设置,这个销毁对象需要我们手动调用一个方法进行销毁。
首先在Java类中定义销毁后执行的方法。
// 被销毁时执行的方法。
public void destroyMethod() {
System.out.println("第五步, 执行销毁的方法");
}
在Xml中配置,同上面第三步初始化一样,它也有一个属性,叫 destory-method 对应的值也就是你要执行的方法。
<bean id="orders" class="com.w.spring5.bean.Orders" init-method="initMethod"
destroy-method="destroyMethod">
<property name="ordersName" value="手机" />
bean>
在配置之后,他并不会自动销毁,这个只是配置你销毁后所执行的方法。所以需要在Java测试类中手动去配置销毁代码。
有一个方法叫close().
@Test
public void ordersTest() {
// ApplicationContext context = new ClassPathXmlApplicationContext("beans4.xml");
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans4.xml");
Orders orders = context.getBean("orders", Orders.class);
System.out.println("第四步,获取Bean容器并且使用");
System.out.println(orders.toString());
// 手动让Bean的实例销毁。
context.close();
}
把ApplicationContext 替换为 子接口的实现类 ClassPathXmlApplication , close 是 他特有的方法。如果不改,则需要
向下转型,才可以使用close
Application context = new ClassPathXmlApplicationContext("beans4.xml");
((ClassPathXmlApplicationContext) context).close();
Java 类
package com.w.spring5.bean;
public class Orders {
private String ordersName;
public void setOrdersName(String ordersName) {
this.ordersName = ordersName;
System.out.println("第二部, 调用set方法,进行属性注入");
}
public Orders() {
System.out.println("第一步, 执行无参构造创建Bean实例");
}
// 创建执行的初始化方法
public void initMethod() {
System.out.println("第三步, 执行初始化的方法。");
}
// 被销毁时执行的方法。
public void destroyMethod() {
System.out.println("第五步, 执行销毁的方法");
}
@Override
public String toString() {
return "Orders{" +
"ordersName='" + ordersName + '\'' +
'}';
}
}
XML配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation=
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<bean id="orders" class="com.w.spring5.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
<property name="ordersName" value="手机" />
bean>
beans>
Java测试类
@Test
public void ordersTest() {
// ApplicationContext context = new ClassPathXmlApplicationContext("beans4.xml");
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans4.xml");
Orders orders = context.getBean("orders", Orders.class);
System.out.println("第四步,获取Bean容器并且使用");
System.out.println(orders.toString());
// 手动让Bean的实例销毁。
context.close();
}
第一步, 执行无参构造创建Bean实例
第二部, 调用set方法,进行属性注入
第三步, 执行初始化的方法。
第四步,获取Bean容器并且使用
Orders{ordersName='手机'}
第五步, 执行销毁的方法
在第三步初始化之前,会有一个初始化前方法, 在第三步初始化之后,会有一个初始化后方法。
(1) 通过构造器去创建Bean的实例(无参数的构造)。
(2) 如果 Bean 中有属性,就去设置他对应的值。 或者对Bean的一些引用(调用set方法)
(3) 把Bean的实例,传递给Bean的后置处理器的方法。
(4) 调用Bean的初始化方法。(需要进行配置)
(5) 把Bean的实例,传递给Bean的后置处理器的方法。
(6) Bean可以使用(对象获取到了)
(7) 当容器关闭的时候,调用Bean销毁的方法。(销毁, 需要自己进行配置销毁。)
(1) 创建一个类,去实现一个接口( BeanPostProcessor )
在BeanPostProcessor接口里 有两个方法,默认实现
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.beans.factory.config;
import org.springframework.beans.BeansException;
import org.springframework.lang.Nullable;
public interface BeanPostProcessor {
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
而在我们的MyBeanPost里,只需要对这两个方法进行重写
package com.w.spring5.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
也就是在初始化之前,会执行 postProcessBeforeInitialization 这个方法
在初始化之后,会执行 postProcessAfterInitialization 这个方法
为了更好的区分,我们在初始化前的 postProcessBeforeInitialization 方法里加上一个输出语句,初始化后的 postProcessAfterInitialization 也一样加上一个输出语句
package com.w.spring5.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 在执行前输出一句话,为了更好的区分
System.out.println("初始化执行前的后置处理器");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 在执行后输出一句话,为了更好的区分
System.out.println("初始化执行后的后置处理器");
return bean;
}
}
其实他就是一个普通类,所以我们需要在XML配置文件中配置这个类,让他可以实际用起来.
<bean id="myBeanPost" class="com.w.spring5.bean.MyBeanPost" />
这是一个新建的Bean,不是内部Bean,新建的myBeanPost这个Bean,在创建的时候,发现 MyBeanPost这个类,实现了 BeanPostProcessor 这个接口,呢么Spring就会认为他是一个后置处理器,他会帮你把 所有的Bean都自动的添加上,后置处理器这个功能,并且执行里面的方法。
Java测试代码不变
第一步, 执行无参构造创建Bean实例
第二部, 调用set方法,进行属性注入
初始化执行前的后置处理器
第三步, 执行初始化的方法。
初始化执行后的后置处理器
第四步,获取Bean容器并且使用
Orders{ordersName='手机'}
第五步, 执行销毁的方法
对比于之前的五步,我们无非发现,在第三步的前后,分别加上了一个执行前的方法和一个执行后的方法。这就是后置处理器要做的事情。
根据指定规则装配,(属性类型或者属性名称),Spring 自动将匹配的属性值进行注入,这个过程就叫做自动装配。
说白了就是简化开发。
首先新建一个包,起名为 autowire, 在包里新建类, Emp代表员工,Dept代表部门。
因为一个员工只有一个部门,而一个部门有多个员工,所以类的设计为下
package com.w.spring5.autowire;
public class Dept {
@Override
public String toString() {
return "Dept{}";
}
}
package com.w.spring5.autowire;
public class Emp {
private Dept dept;
public void setDept(Dept dept) {
this.dept = dept;
}
@Override
public String toString() {
return "Emp{" +
"dept=" + dept +
'}';
}
}
XML配置里装配Bean对象。 这一步是烂熟于心的。
<bean id="emp" class="com.w.spring5.autowire.Emp">
<property name="dept" ref="dept" />
bean>
<bean id="dept" class="com.w.spring5.autowire.Dept">
bean>
Java测试类
@Test
public void empAndDept() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans5.xml");
Emp emp = context.getBean("emp", Emp.class);
System.out.println(emp.toString());
// Emp{dept=Dept{}}
}
呢么自动装配,就不需要在Bean里去写property了
在Bean标签里有一个属性叫, autowire 他有两个对应且常用的值, byName,byType
byName: 根据属性名称注入对应的值。
byType: 根据属性类型注入对应的值。
<bean id="emp" class="com.w.spring5.autowire.Emp" autowire="byName">
bean>
<bean id="dept" class="com.w.spring5.autowire.Dept">
bean>
只需要这样书写,就可以实现 根据名称自动注入属性的写法。
注意点:
类里面的属性 名称,要和 注入的id完全一致
打个比方
package com.w.spring5.autowire;
public class Emp {
private Dept dept;
public void setDept(Dept dept) {
this.dept = dept;
}
}
拿该类举例, private Dept dept。 呢么对应的名称是 dept,要想根据名称注入属性,呢么 所对应的Bean对象的Id,就必须和该属性的名称完全一致。
<bean id="dept" class="xxxxx">bean>
这样在装配了autowire之后,才能完成自动装配。
<bean id="emp" class="xxxxx" autowire="byName">
bean>
<bean id="dept" class="xxxxx">bean>
最后在Java代码测试类中进行测试
// Emp{dept=Dept{}}
与手动装配完全一致。所以为自动装配成功。
在根据 byName的基础上,进行更改。
<bean id="emp" class="xxxxx" autowire="byType">
bean>
<bean id="dept" class="xxxxx">bean>
这是唯一的。如果你根据类型创建,而 对应类型的 dept却有两个,如下。
<bean id="emp" class="xxxxx" autowire="byType">
bean>
<bean id="dept" class="xxxxx">bean>
<bean id="dept" class="xxxxx">bean>
因为他是根据类型做匹配,你出现多个,他并不知道匹配哪一个,所以报错。要注意这个问题。
在开发中,我们在一个XML配置文件里,可能会配置很多的类,很多的东西,但是有一些写死的东西,固定的东西, 有可能会去重复的书写,比如JDBC操作数据库的数据库驱动链接。 数据库地址,账号密码,端口号等等一些基本上是写死的东西,我们就可以把它写到一个properties文件里,然后在XML文件中去读取这个文件的一个过程,这就是读取外部属性文件。
首先肯定需要引入德鲁伊连接池的依赖 或者 jar包
然后要去XML配置文件里创建德鲁伊的相关Bean对象,和对应的属性注入。
在连接池中有一些最基本的属性
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="" />
<property name="url" value="" />
<property name="username" value="" />
<property name="password" value="" />
bean>
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mydb" />
<property name="username" value="root" />
<property name="password" value="root" />
bean>
在配置之后,发现,这些值都是固定死的,呢么做修改的时候,或者下次使用的时候,需要直接拷贝,很麻烦,呢么我们可以写到一个外部配置文件里。
所以有了下一步,引入外部的文件方式
(1)创建外部是属性文件。properties格式的文件。然后在里面配置数据库的信息。
prop.driverClass=com.mysql.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/mydb
prop.username=root
prop.password=root
(2)把外部 properties 文件引入到spring的配置文件中
引入名称空间,context
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
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/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
beans>
在spring的配置文件中,使用标签引入外部配置文件
<context:property-placeholder location="classpath:jdbc.properties" />
配置连接池
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClass}" />
<property name="url" value="${prop.url}" />
<property name="username" value="${prop.username}" />
<property name="password" value="${prop.password}" />
bean>
<context:property-placeholder location="classpath:jdbc.properties" />
注解是你代码里一些特殊的标记
格式: @注解名称(属性名称=属性值,…)
注解可以作用在类上,或者方法上面,属性上面。
使用的目的:为了简化XML的开发,更优雅的代码。
Spring 针对 Bean 管理中创建对象提供的注解
- @Component
- @Service
- @Controller
- @Repository
我们上面的四个注解,他们的功能是一样的,都可以用来创建你的Bean实例。
只是用在了不同的构架上面,用错了也无所谓。只是为了让开发人员更加清晰当前组件所扮演的角色。
第一步 引入依赖
需要引入AOP的依赖
到目前为止,所引入到项目的jar包有以下
第二步 开启组件扫描
在XML配置文件中引入 context的命名空间
<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 http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.w.spring5" />
<context:component-scan base-package="com.w.spring5, com.w.spring4, com.w.spring3" />
beans>
开启注解扫描需要使用到 context:component-scan 这个标签, 有一个属性 base-package 里面是要扫描的包下的路径。
第三步 创建类,在类上面添加注解
在spring5的包下,创建三个包,dao,service,textdemo
spring5并非指定的,而是他们的上一级目录,这个名字可以随便起,我在类路径下的包全路径为:
com.w.spring5.dao
com.w.spring5.service
com.w.spring5.textdemo
在service包下,创建UserService类,并且创建方法 add() 打印输出一句话,并且加上@Service注解
package com.w.spring5.service;
import org.springframework.stereotype.Service;
@Service(value="userService")
public class UserService {
public void add() {
System.out.println("add执行了...");
}
}
在注解里可以有一个值,value, value也可以不写,如果不写的话,默认值就是你的类名称,把类名称的首字母小写,其他不变
UserService ----> userService
@Service(value="userService")
呢么就等同于xml配置bean里面的 id属性,相当于起别名。
<bean id="userService" class="xxx">bean>
最后去进行测试
package com.w.spring5.testdemo;
import com.w.spring5.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void userServiceTest() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.add(); // add执行了...
}
}
也就代表了被@Service 托管的Spring对象,是可以获取到的。
首先,我们需要导入一个依赖,AOP的jar包,在导入之后,因为Spring启动扫描是需要 context命名空间,所以我们需要在xml文件中引入对应的命名空间,之后,开启spring的扫描,去扫描对应包下的被Spring托管的对象。然后我们需要在需要被托管的对象的类名称上,加上对应的注解,@Service等注解。看情况加,之后就可以去通过Java测试类去获取到该Bean对象。
在第一步的导入jar包,和第二步导入对应命名空间之后, 有一个组件扫描,这里我们会详细去说一下组件扫描的一些细节配置,我们可以约束那些类哪些配置可以不进行扫描,哪一些去扫描等等。
举两个例子,示例一:
<context:component-scan base-package="com.wang" use-default-filters="false">
<context:include-filter type="annotation"
expression="org.springframework.sterertype.Controller" />
context:component-scan>
可以看到有一个新的属性,
use-default-filters=“false” 表示,现在不使用默认的filters,使用自己配置的filters
context:include-filter 通过 这个,设置扫描哪一些内容。
type=annotation, 表示根据注解来进行扫描。
expression=“org.springframework.sterertype.Controller”,表示扫描的话,只扫描带 @Controller 注解的扫描
示例二:
<context:component-scan base-package="com.wang">
<context:exclude-filter type="annotation"
expression="org.springframework.sterertype.Controller" />
context:component-scan>
context:exclude-filter 是不包含的意思,表示不包含 @Controller注解的标识的类。 也就是不去扫描被@Controller标注的类。
基于注解实现的属性注入, 这是四个最常用的注解, 但是这前三个类型都是基于对象属性去注入的,若要注入普通类型,如String,int等。则可以使用@Value() 后续会说。
(1) @Autowired
根据属性类型,进行自动装配。
(2) @Qualifier
根据属性名称,进行注入。
(3) @Resource
可以根据类型注入,也可以根据名称注入。
(4) @Value
注入普通类型。
把service 和 dao 层的对象创建,在service 和 dao 类添加创建对象注解。
在UserService类里,定义UserDao类型的属性。可以通过接口new实现类,多态的方式实现。在属性上方使用我们的注解就可以做到。 不需要添加 set 方法。这一步已经被封装过了。
添加注解。
书写测试类测试 UserDaoImpl的add方法。
/* UserService */
package com.w.spring5.service;
import com.w.spring5.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service(value="userService")
public class UserService {
// 创建UserDao类型的属性
@Autowired
private UserDao userDao;
public void add() {
System.out.println("add执行了");
userDao.add();
}
}
/* UserDao --- interface */
package com.w.spring5.dao;
public interface UserDao {
void add();
}
/* UserDaoImpl */
package com.w.spring5.dao;
import org.springframework.stereotype.Repository;
// UserDao的实现类
@Repository
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("UserDaoImpl add.......");
}
}
/* JavaTest */
@Test
public void UserDaoImpl() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.add();
/*
add执行了
UserDaoImpl add.......
*/
}
这个@Qualifier注解的使用,和上面的@Autowired要在一起进行使用。
因为一个接口可以有多个实现类,在一个接口只有一个实现类的时候,我们的@Autowired可以根据类型,去自动注入对应的属性。但是一旦有多个实现类,他不知道注入哪个,会产生错误,这个时候就可以用到我们的@Qualifier,通过名称注入了。这样可以指定,唯一,不会产生莫名的错误。
在原有代码的基础上,我们在UserDaoImpl这个实现类上的类注解,@Repository 里,加上 value属性,给他指定id名称。
import org.springframework.stereotype.Repository;
// UserDao的实现类
@Repository(value = "userDaoImpl")
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("UserDaoImpl add.......");
}
}
然后在UserService类里,通过名称给他注入属性。 只需要加上@Qualifier(value = “” ) ,value对应的值,是你要注入类型的,并且在Spring容器中存在的,id名称。也可以说是别名。
package com.w.spring5.service;
import com.w.spring5.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service(value="userService")
public class UserService {
// 创建UserDao类型的属性
@Autowired
@Qualifier(value = "userDaoImpl")
private UserDao userDao;
public void add() {
System.out.println("add执行了");
userDao.add();
}
}
在其余结构不变的情况下,去执行我们原有的Java测试代码。得到的结果如下
add执行了
UserDaoImpl add.......
可以看到,依然是可以被注入的。
若要使用Resource 则需要导入包
import javax.annotation.Resource;
如果要根据类型注入,直接加上Resource,
若是根据名称注入,@Resource(name = “”) name对应的属性为 你要注入的名称 id。
在基础属性上加上@Value("") 属性为对应的值。
@Value("张三")
private String name;
@Value(value = "张三")
是一个效果
// 此时, name为张三。
在spring5下,创建一个包,叫config。这里面存放我们的配置类,配置类名称为 SpringConfig。当然。这个名称可以随便起。
你只有一个完整的类,Spring肯定不会去认识你这个类,所以,我们需要加上一个注解,@Configuration
这样他会把你当成一个Spring的配置类。
然后,使用@ComponentScan(basePackages = {“com.w.spring5”}) 来表示要扫描的包路径, 里面对应的属性是一个对象类型,表明了包类路径。
package com.w.spring5.config;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = {"com.w.spring5"})
public class SpringConfig {
}
这个时候,我们就可以把beans.xml 进行一个删除的操作
删除之后,我们的测试类代码,就会跟之前有一点点的不一样。
ApplicationContext 不变, 但是 new 的对象变成了,AnnotationConfigApplicationContext
Annotation 是注解的意思,他让你在内部去传入一个配置类,所以我们通过 SpringConfig.class 反射的方式,
把我们写的配置类传给他,剩下的几步不变。
import com.w.spring5.config.SpringConfig;
import com.w.spring5.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MyTest {
@Test
public void userDaoImpl() {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean("userService", UserService.class);
userService.add();
System.out.println(userService.toString());
/*
UserService add....
UserDaoImpl add.....
UserService{userDao=com.w.spring5.dao.UserDaoImpl@4c012563, name='张三'}
* */
}
}
什么是AOP
(1) 面向切面编程(面向方面编程) 是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
有一个登录的场景,需要我们去做。在前端提交的form表单,获取到账号密码之后,我们读取数据库,然后判断是否存在对应的账号密码,如果成功了,呢么我们就定向到登陆成功的页面,反之,让他重新输入账号密码。
突然有一天,老板让你去增加一个功能, 增加一组登录日志功能,在不改变原有代码的基础上,去新建一个模块,这个模块专门管理登录日志这个功能,然后通过AOP面向切面的方式给他添加到代码里,这样,我们没有对原有的代码进行改动,也实现了新功能,假设有一天不需要这个功能了,我们只需要撤出对应的AOP配置,呢么依然不需要对原有的代码进行一点的改动。这就是AOP的使用场景。
AOP的底层,使用到了动态代理方式。
有两种情况的动态代理。
第一种情况。 有接口的情况下使用JDK动态代理。
第二种情况,没有接口的情况下使用CGLIB动态代理。
1,使用 JDK 动态代理,使用 Proxy类里的方法,来创建出代理对象。
在Proxy类里面有一个方法叫 newProxyInstance(ClassLoader loader, 类>[] interfaces, InvocationHandler h)
这个方法里有三个参数, loader, interfaces, h
第一个参数,得到当前的类加载器。
第二个参数,增强方法所在的类,这个类,实现的呢个接口。 支持多个接口。
第三个参数,实现这个接口,InvocationHandler,创建代理对象。写我们增强的方法。
下面使用代码来编写这些东西
(1)创建接口,定义方法
package com.w.spring5;
public interface UserDao {
int add(int a, int b);
String update(String id);
}
(2)创建接口实现类,实现方法
package com.w.spring5;
public class UserDaoImpl implements UserDao {
@Override
public int add(int a, int b) {
return a + b;
}
@Override
public String update(String id) {
return id;
}
}
(3)使用Proxy类创建一个接口的代理对象
package com.w.spring5;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class JdkProxy {
public static void main(String[] args) {
UserDao userDao = new UserDaoImpl();
Class[] interfaces = {UserDao.class};
// 创建接口实现类的代理对象。
UserDao userDao1 = (UserDao) Proxy.newProxyInstance(
JdkProxy.class.getClassLoader(),
interfaces,
new UserDaoProxy(userDao)
);
userDao1.add(1, 2);
}
}
// 创建代理对象的代码
class UserDaoProxy implements InvocationHandler {
// 把创建的是谁的代理对象,把谁给他传递过来。
// 通过有参构造传递。
private Object object;
public UserDaoProxy(Object o) {
this.object = o;
}
// 增强的逻辑
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法前
System.out.println("方法之前执行...." + method.getName() + " :传递的参数 " +
Arrays.toString(args));
// 被增强的方法
Object invoke = method.invoke(object, args);
// 方法后
System.out.println("方法之后执行...." + object);
return invoke;
}
}
一步一步解析一下,首先在接口和实现类,创建好之后,我们写了一个代理类,JdkProxy
通过 newProxyInstance 这个方法,来实现接口的代理对象。为了更加灵活,我们新建一个类
UserDaoProxy, 让他实现InvocationHandler, 然后我们通过有参构造器的方式,给他把传进来的对象赋值给属性。
然后写对应的增强逻辑,重写InvocationHandler 的 invoke 方法, 然后把该方法返回。
然后
UserDao userDao1 = (UserDao) Proxy.newProxyInstance(
JdkProxy.class.getClassLoader(),
interfaces,
new UserDaoProxy(userDao)
);
通过这种方式,强转为UserDao类型。调用其对应的方法。
(1) 连接点
类里面的那些方法可以被增强,呢么这些方法就叫做连接点。
(2) 切入点
实际被真正增强的方法,他就被称为切入点。
(3) 通知(增强)
实际增强的逻辑部分,成为通知(增强)
有多种通知类型:
前置通知
前置通知是什么,比如现在想增强User类里的add()方法,呢么就表示,在add()执行前,会执行这个前置通知。
后置通知
后置通知和前置通知一样,只是把通知的位置翻了过来,会在执行后去执行这个后置通知。
环绕通知
环绕通知相当于前置通知和后置通知的结合体,会在前和后分别去进行一个通知。
异常通知
现在想增强一下User的add()方法,当add方法出现异常的时候,他就会执行。
最终通知
就类似于我们Java代码的 try catch - finally 中的 finally,不管怎么样,他最终都会执行。
(4) 切面
切面他做了一个动作,把我们的通知应用到切入点的一个过程,他就叫切面
Spring框架一般基于 AspectJ 实现 针对AOP的一些操作。
基于 AspectJ 实现AOP操作。
在项目工厂里引入AOP相关的一些依赖。
spring-aspects-5.2.0.RELEASE.jar
com.springsource.net.sf.cglib-2.2.0.jar
以及jar包不好找,俩maven依赖
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.6.8version>
dependency>
<dependency>
<groupId>aopalliancegroupId>
<artifactId>aopallianceartifactId>
<version>1.0version>
dependency>
切入点表达式
举例一: 对 com.w.dao.BookDao 类 里面的add方法进行增强。
execution(* com.w.dao.BookDao.add(..));
第一个 * 号表示,可以是任意的权限修饰符,权限修饰符是可以省略不写的,而方法返回值类型必须去写(在此感谢 @斯巴鲁冲冲冲 提供的错误。)。然后后面跟类全路径,在往后可以在类全路径后面直接通过 . 来add 调用该方法,然后在方法后面跟小括号,() 里面的 … 代表了方法中的参数。
举例2: 我想对com.w.dao.BookDao 类里面的所有方法进行增强。
只需要把ADD变为 *
execution(* com.w.dao.BookDao.*(..));
举例3: 我想对com.w.dao包里面的所有类,类里面的所有方法都进行增强。
execution(* com.w.dao.*.*(..));
创建一个类,类里面定义一个方法。实现类中的方法增强。
package com.w.spring5.aopano;
public class User {
public void add() {
System.out.println("add...");
}
}
创建一个增强类(编写你增强的逻辑)
在增强类里面,创建方法,让不同的方法代表不同的通知类型。
package com.w.spring5.aopano;
public class UserProxy {
public void before() {
System.out.println("before......");
}
}
User类是一个被增强的类。
UserProxy是一个增强类。 在UserProxy方法里有一个before方法。
需求就是在add方法之前,把before方法做一个执行。 还记得我们前面说的前置围绕吗。
进行通知的配置
因为我们是基于注解做到的。所以我们现在spring的配置文件中,做一件事情,开启注解的扫描。
<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 http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.w.spring5" />
beans>
使用注解来创建User 和 UserProxy对象。
package com.w.spring5.aopano;
import org.springframework.stereotype.Controller;
@Controller
public class UserProxy {
public void before() {
System.out.println("before......");
}
}
package com.w.spring5.aopano;
import org.springframework.stereotype.Controller;
@Controller
public class User {
public void add() {
System.out.println("add...");
}
}
在增强的类上面添加一个注解 @Aspect
package com.w.spring5.aopano;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Controller;
@Controller
@Aspect // 生成代理对象。
public class UserProxy {
public void before() {
System.out.println("before......");
}
}
在Spring的配置文件中开启生成代理对象
在这个过程中,还需要用到一个命名空间,这个命名空间叫做AOP,所以需要引入对应的命名空间。
<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 http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.w.spring5" />
beans>
<aop:aspectj-autoproxy />
配置不同类型的通知,这里是前置围绕
在增强类的里面,在作为通知方法上面添加通知类型注解,使用切入点表达式配置。
package com.w.spring5.aopano;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Controller;
@Controller
@Aspect // 生成代理对象
public class UserProxy {
@Before("execution(* com.w.spring5.aopano.User.add(..))")
public void before() {
System.out.println("before......");
}
}
在增强的方法上面,添加@Before前置环绕注解,然后里面有一个value值,这个value可写可不写,对应的值是 切入点表达式。
进行Java测试类代码的书写
import com.w.spring5.aopano.User;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void aopTestAno() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = context.getBean("user", User.class);
user.add();
/*
before......
add...
* */
}
}
可以看到前置环绕以及成功的添加。现在捋一下步骤。
第一步,我们首先创建了一个被增强的类。User类。 在User类里面有一个需要被增强的方法,add方法。
第二步,我们又创建了一个增强类,UserProxy类,里面有对应的增强逻辑。
第三步,在这两个类上,标注上Spring的注解,让他被Spring托管。创建对应的对象。
第四步,我们需要在XML配置文件里,开启对应的注解扫描。
第五步,引入代理对象创建所需的命名空间,aop。 并且开启代理对象的创建。在XML里。
第六步,在增强类里,标注上@Aspect注解,表示生成代理对象。然后在需要向被增强类User类里添加的方法上标注@Before() 在其里添加上对应的表达式。
第七步,书写测试类进行测试。
需要注意的是,我们在测试的时候,获取的类,是被增强的类。拿上面举例子,也就是我们的User类。
但是别忘了,不光前置环绕一种通知。共有五种通知类型。呢么下面在原有代码的基础上去补其他几种。
// 前置环绕
@Before("execution(* com.w.spring5.aopano.User.add(..))")
// 后置环绕也可以叫做最终通知
@After("execution(* com.w.spring5.aopano.User.add(..))")
// 环绕通知
@Around("execution(* com.w.spring5.aopano.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕前......");
// 被执行的方法
proceedingJoinPoint.proceed();
System.out.println("环绕后......");
}
// 后置通知/返回通知
@AfterReturning("execution(* com.w.spring5.aopano.User.add(..))")
// 在方法抛出异常后执行
@AfterThrowing("execution(* com.w.spring5.aopano.User.add(..))")
完整的Java代码
package com.w.spring5.aopano;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Controller;
@Controller
@Aspect // 生成代理对象
public class UserProxy {
@Before("execution(* com.w.spring5.aopano.User.add(..))")
public void before() {
System.out.println("before......");
}
@After("execution(* com.w.spring5.aopano.User.add(..))")
public void after() {
System.out.println("after......");
}
@AfterReturning("execution(* com.w.spring5.aopano.User.add(..))")
public void afterReturning() {
System.out.println("afterReturning......");
}
@AfterThrowing("execution(* com.w.spring5.aopano.User.add(..))")
public void afterThrowing() {
System.out.println("afterThrowing......");
}
// 环绕通知
@Around("execution(* com.w.spring5.aopano.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕前......");
// 被执行的方法
proceedingJoinPoint.proceed();
System.out.println("环绕后......");
}
}
侧重于异常来讲解一下,我们在add方法中,手动给他弄出一个异常, 比如拿一个整数 去 除以 0;
package com.w.spring5.aopano;
import org.springframework.stereotype.Controller;
@Controller
public class User {
public void add() {
int i = 10 / 0;
System.out.println("add...");
}
}
然后注意测试类的结果。
毋庸置疑,他会给我们抛出一个异常
java.lang.ArithmeticException: / by zero
但是这不是重点,因为这个异常是我们自己弄出来的,我们清楚,重点是看执行的内容。
环绕前......
before......
after......
afterThrowing......
可以注意到,他没有执行add方法,和add方法后应该坏绕的结果。 但是他在add方法前,环绕通知和异常通知执行了,但是终止在了异常通知部分。 after依然会执行。
相同的切入点进行抽取。
在上面的例子中,我们所有的切入点表达式,都是重复单一的。这样写,若是以后有改动,呢么所有的表达式都需要进行改动,非常麻烦,所以我们把切入点抽取出来
// 相同的切入点进行抽取
@Pointcut(value = "execution(* com.w.spring5.aopano.User.add(..))")
public void pointDemo() {
}
定义一个方法,在上面标注@Pointcut,后面写上重复使用的表达式。然后我们就可以在下次使用切入点表达式的时候,去直接调用该方法( 需要加上方法括号 )。如:
@Before("pointDemo()")
public void before() {
System.out.println("before......");
}
@After("pointDemo()")
public void after() {
System.out.println("after......");
}
这样写的好处毋庸置疑,在下次需要改动的时候,我们只需要对pointDemo上@Pointcut里的表达式进行更改,就可以使得下面的表达式生效。更加方便了我们代码的书写。
我们定义一个类,PersonProxy,这是一个增强类。
package com.w.spring5.aopano;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class PersonProxy {
// 前置通知
@Before("execution(* com.w.spring5.aopano.User.add(..))")
public void before() {
System.out.println("PersonBefore......");
}
}
他做了一个前置通知的效果,但是别忘了。我们在这个增强类的同时,还有一个增强类,UserProxy。
我们假设要做到一个优先级的顺序,怎么做。
首先,我们在增强类的上面标注一个注解,@Order([值]),这个值,是数字类型的值,这个数字的值越小,呢么他的优先级就越高。
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Controller;
@Controller
@Aspect // 生成代理对象
@Order(1)
public class UserProxy {
// 相同的切入点进行抽取
@Pointcut(value = "execution(* com.w.spring5.aopano.User.add(..))")
public void pointDemo() {
}
@Before("pointDemo()")
public void before() {
System.out.println("before......");
}
@After("pointDemo()")
public void after() {
System.out.println("after......");
}
@AfterReturning("pointDemo()")
public void afterReturning() {
System.out.println("afterReturning......");
}
@AfterThrowing("pointDemo()")
public void afterThrowing() {
System.out.println("afterThrowing......");
}
// 环绕通知
@Around("pointDemo()")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕前......");
// 被执行的方法
proceedingJoinPoint.proceed();
System.out.println("环绕后......");
}
}
package com.w.spring5.aopano;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Aspect
@Order(2)
public class PersonProxy {
// 前置通知
@Before("execution(* com.w.spring5.aopano.User.add(..))")
public void before() {
System.out.println("PersonBefore......");
}
}
呢么,如果按照上面的方式来进行增强,我们执行的顺序,应该是先去实现 UserProxy,然后才是PersonProxy。
我们执行测试代码得到结果:
环绕前......
before......
PersonBefore......
add...
环绕后......
after......
afterReturning......
看起来确实如此,PersonBefore在 before后面执行了。
我们把Order里对应的值的顺序反过来。然后执行测试代码。得到的结果是:
PersonBefore......
环绕前......
before......
add...
环绕后......
after......
afterReturning......
可以明确的看到,PersonBefore , 在前面执行了。
(1)创建两个类,增强类和被增强类。创建对应的add方法。
package com.w.spring5.aopano;
public class UserProxy {
public void before() {
System.out.println("User Proxy方法before执行了......");
}
}
package com.w.spring5.aopano;
public class User {
public void add() {
System.out.println("User Add方法执行了......");
}
}
(2)在配置文件中创建两个对象。
<bean id="user" class="com.w.spring5.aopano.User">
bean>
<bean id="userProxy" class="com.w.spring5.aopano.UserProxy">
bean>
(3)在spring配置文件中配置切入点
<aop:config>
<aop:pointcut id="p" expression="execution(* com.w.spring5.aopano.User.add(..))"/>
<aop:aspect ref="userProxy">
<aop:before method="before" pointcut-ref="p" />
aop:aspect>
aop:config>
拆分讲解,首先我们有一个标签叫 aop:config 表示了一个aop的配置,
然后我们配置一个切入点,还记得AOP-操作术语吗。实际被真正增强的方法,他就被称为切入点
呢么后面的表达式,就是你要实际加强的方法,id 相当于起别名。
然后我们需要配置一个切面,实际增强的逻辑部分,成为通知(增强) 告诉他你的增强类是哪一个,然后里面有一个
标签叫做 aop:before,这就是你的前置环绕, 两个参数, method 对应你类里的增强方法, pointcut-ref 表示你要被增强类的方法是什么。
最终我们进行测试
@Test
public void myTest() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = context.getBean("user", User.class);
user.add();
/*
User Proxy方法before执行了......
User Add方法执行了......
* */
}
可以看到,他已经进行了前置环绕。
如果其他的环绕,其实也是大同小异的
我们只需要更改xml配置
<aop:config>
<aop:pointcut id="p" expression="execution(* com.w.spring5.aopano.User.add(..))"/>
<aop:aspect ref="userProxy">
<aop:before method="before" pointcut-ref="p" />
aop:aspect>
aop:config>
也就是更改
<aop:before method="before" pointcut-ref="p" />
这一行的配置
aop:after
最终环绕。等等。以此类推
package com.w.spring5.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan(basePackages = {"com.w.spring5"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AopConfig {
}
首先我们要让Spring知道这是一个配置类肯定是没错的,而且还要开启直接扫描。
@EnableAspectJAutoProxy(proxyTargetClass = true) 是开启代理对象。代替掉了XML的
<aop:aspectj-autoproxy />
这一句话,它里面的值,如果不写的情况下,默认为 false,所以我们需要给他打开,让他生成代理对象。
这样我们就不需要XML配置文件了。
在AOP完结后,我们需要重点掌握的一些内容:
com.springsource.net.sf.cglib-2.2.0.jar
commons-logging-1.2.jar
druid-1.1.22.jar
mysql-connector-java-5.1.49.jar
spring-aop-5.2.0.RELEASE.jar
spring-aspects-5.2.0.RELEASE.jar
spring-beans-5.3.8.jar
spring-context-5.3.8.jar
spring-core-5.3.8.jar
spring-expression-5.3.8.jar
<dependencies>
<dependency>
<groupId>javax.annotationgroupId>
<artifactId>javax.annotation-apiartifactId>
<version>1.2version>
dependency>
<dependency>
<groupId>org.junit.jupitergroupId>
<artifactId>junit-jupiterartifactId>
<version>RELEASEversion>
<scope>testscope>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.6.8version>
dependency>
<dependency>
<groupId>aopalliancegroupId>
<artifactId>aopallianceartifactId>
<version>1.0version>
dependency>
dependencies>
什么是JdbcTemplate
准备工作
(1) 引入相关的Jar包。
(2) 创建数据库user_db / 在spring配置文件中配置数据库连接池
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="jdbc:mysql:///user_db" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="username" value="root" />
<property name="password" value="root" />
bean>
(3) 配置JdbcTemplate对象,对象注入DataSource
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
bean>_
(4) 创建Service类,创建Dao类,在dao注入jdbcTemplate对象。
首先创建service 和 dao 包。
然后创建对应的对象。首先要有Dao接口 和 Impl实现类
package com.w.spring5.dao;
public interface BookDao {
}
package com.w.spring5.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository("bookDaoImpl")
public class BookDaoImpl implements BookDao {
// 注入JDBCTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
}
我们在Dao实现类里,注入了JdbcTemplate。
然后我们在Service调Dao层的实现类
package com.w.spring5.service;
import com.w.spring5.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service("bookService")
public class BookService {
// 注入Dao
@Autowired
private BookDao bookDao;
}
在数据库里新建一个表,t_book 有三个字段,user_id, username, ustatus. 用户id ,用户名称, 和用户状态。
分别对应 int varchar varchar.
一般一个表会对应一个实体类,我们来进行实体类的书写。
(1) 对应数据库,创建实体类。
我们新建一个包,entity,然后新建一个类 Book
package com.w.spring5.entity;
public class Book {
private String userId;
private String userName;
private String uStatus;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getuStatus() {
return uStatus;
}
public void setuStatus(String uStatus) {
this.uStatus = uStatus;
}
@Override
public String toString() {
return "User{" +
"userId='" + userId + '\'' +
", userName='" + userName + '\'' +
", uStatus='" + uStatus + '\'' +
'}';
}
}
(2)编写service 和dao
在dao里面进行数据库添加的操作。
首先我们要写一个对应的接口,接口有一个参数,类型为:Book类型。为了方便们传值。
package com.w.spring5.dao;
import com.w.spring5.entity.Book;
public interface BookDao {
void add(Book book);
}
然后我们编写对应的 BookDaoImpl 类。
我们让这个类去实现BookDao这个接口,并且重写add方法。在方法里,我们可以使用提前注入好的 jdbcTemplate 属性。调用一个方法,update。 update有两个参数,第一个是String 类型的 sql语句,第二个 是一个不定长参数。
package com.w.spring5.dao;
import com.w.spring5.entity.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository("bookDaoImpl")
public class BookDaoImpl implements BookDao {
// 注入JDBCTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void add(Book book) {
// sql语句
String sql = "insert into t_book values(?, ?, ?)";
Object[] args = {book.getUserId(), book.getUserName(), book.getuStatus()};
// 调用方法实现
int update = jdbcTemplate.update(sql, args);
System.out.println("更新成功,最终添加了: " + update + " 记录");
}
}
jdbcTemplate.update(String sql, Object… args);
第二个参数是一个不定长参数,我们可以通过上面的代码看到 我们的sql语句,String sql = "insert into t_book values(?, ?, ?)"
其中,在values里,有三个 ? , 表示了占位符,每一个问号,对应一个值,呢么我们就可以在 args 这个参数里实现,有几个问号,写几个值,按顺序来写。
我们在BookService里,添加一个add方法,让他去调用自动注入的bookDao.add方法,并且传入一个(book) 类型的对象。
package com.w.spring5.service;
import com.w.spring5.dao.BookDao;
import com.w.spring5.entity.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service("bookService")
public class BookService {
// 注入Dao
@Autowired
private BookDao bookDao;
// 添加的方法
public void addBook(Book book) {
bookDao.add(book);
}
}
最终去写测试代码。
import com.w.spring5.entity.Book;
import com.w.spring5.service.BookService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTestBook {
@Test
public void testBook() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
BookService bookService = context.getBean("bookService", BookService.class);
bookService.addBook(new Book("1", "《算法导论》", "存在"));
// 更新成功,最终添加了: 1 记录
}
}
然后我们看表,确实成功添加了。
其实修改和删除功能,跟上面大同小异,无非只是修改了一下 String sql = xxx;的这一条代码。
在原有代码的基础上,我们修改对应的sql语句。
package com.w.spring5.dao;
import com.w.spring5.entity.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository("bookDaoImpl")
public class BookDaoImpl implements BookDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void add(Book book) {
String sql = "delete from t_book where user_id = ?";
// Object[] o = {book.getBookId(), book.getBookName(), book.getBookActive()};
int update = jdbcTemplate.update(sql, book.getBookId());
// System.out.println("共更新了一条数据,更新的内容为: " + book.toString() + ", 共更新了 "
// + update + " 条数据。");
System.out.prizhentln(update);
}
}
所以我们并不需要多余的设定,仅仅需要记住,Update这个方法,可以做到增加,删除和修改即可。
用法完全相同,不同的只有 sql语句的编写。
接下来,我们们去实现以下这几个功能,我们把他们整合到一起。
/*BookDao -> interface*/
package com.w.spring5.dao;
import com.w.spring5.entity.Book;
public interface BookDao {
// 添加功能
void add(Book book);
// 修改功能
void update(Book book);
// 删除功能 delete 根据id删除对应的整行(数据库主键为id)
void delete(int id);
}
/* BookDaoImpl -> class */
package com.w.spring5.dao;
import com.w.spring5.entity.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository("bookDaoImpl")
public class BookDaoImpl implements BookDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void add(Book book) {
String sql = "delete from t_book where user_id = ?";
// Object[] o = {book.getBookId(), book.getBookName(), book.getBookActive()};
int update = jdbcTemplate.update(sql, book.getBookId());
// System.out.println("共更新了一条数据,更新的内容为: " + book.toString() + ", 共更新了 "
// + update + " 条数据。");
System.out.println(update);
}
// 更新数据
@Override
public void update(Book book) {
/*
* 我们更新数据,分别更新了名称和状态,通过id进行更改。id为唯一主键,所以不用担心重复问题
* */
String sql = "update t_book set username=?, ustatus=? where user_id=?";
// 这里的数组顺序要对应上面的占位符顺序,否则要通过下标取。
Object[] o = {book.getBookName(), book.getBookActive(), book.getBookId()};
int update = jdbcTemplate.update(sql, o);
System.out.println("执行更新数据操作成功,共执行: " + update + " 条, 更新的内容为: {"
+ book.getBookName() + ", 更新状态为: " + book.getBookActive() + "}");
}
// 删除数据
@Override
public void delete(int id) {
String sql = "delete from t_book where user_id = ?";
// 这里直接传入的就是一个数字,id,通过id删除对应的数据
int update = jdbcTemplate.update(sql, id);
System.out.println("删除数据成功, 共删除一条数据,数据的id为: " + id);
}
}
/* BookService */
package com.w.spring5.service;
import com.w.spring5.dao.BookDao;
import com.w.spring5.entity.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service("bookService")
public class BookService {
@Autowired
@Qualifier("bookDaoImpl")
private BookDao bookDao;
// 添加
public void addBook(Book book) {
bookDao.add(book);
}
// 更新
public void updateBook(Book book) {
bookDao.update(book);
}
// 删除
public void deleteBook(int id) {
bookDao.delete(id);
}
}
我们书写测试类来进行测试。一定要认真看我在代码里的注释。
import com.w.spring5.entity.Book;
import com.w.spring5.service.BookService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
BookService bookService = context.getBean("bookService", BookService.class);
// 我们更新 id 为 2 的数据,把Java编程实现,更新为Java编程思想。状态从缺货,更为存在。
bookService.updateBook(new Book("2", "Java编程思想", "存在"));
// 执行更新数据操作成功,共执行: 1 条, 更新的内容为: {Java编程思想, 更新状态为: 存在}
// 我们删除id为三的数据
bookService.deleteBook(3);
// 删除数据成功, 共删除一条数据,数据的id为: 3
}
}
现在,我们来查看库中存在的信息。
更新成功,删除成功。
什么是返回某一个值。举个简单的例子吧。我们查询的时候,我想知道,我的表里一共有多少条数据,怎么办。很简单的一个问题,我们都学过mysql, 我们使用聚合函数,COUNT(), 里面放上字段,或者 *。
呢么对应的sql语句为: select count(*) from [表名];
他返回给你的语句结果,是一个数值,一共有多少条记录,呢么我们要的就是这个数值。这也就是说的查询返回单个的值。
我们需要用到JdbcTemplate 的一个方法。
jdbcTemplate.queryForObject(String sql, Class requiredType)
有两个参数,第一个参数,是我们的Sql语句,第二个参数,是一个Class类型,是我们的返回类型的Class,如果返回int类型的数据,呢么就是IntClass,如果是String类型的,则是StringClass。
举个例子,如果我们要一个int类型的返回值
jdbcTemplate.queryForObject(sql, Integer.class);
String呢?
jdbcTemplate.queryForObject(sql, String.class);
现在我们来去写一下案例,实现一个功能。我们查询一下t_book表中有多少条记录。
/* 首先我们先进行接口的编写, 既然要返回一个int数据类型,呢我们就给接口对应上对应的数据类型 */
// 查询表中共有多少条记录
Integer tableCount();
我们继续去实现对应的实现类,也就是BookDaoImpl
/* BookDaoImpl */
// 返回单个数据
@Override
public Integer tableCount() {
String sql = "select count(*) from t_book";
return jdbcTemplate.queryForObject(sql, Integer.class);
}
然后实现我们的逻辑层,ServiceBook类
/* ServiceBook */
// 查询单个数据。也就是实现我们的查询表内有多少条数据
public String selectCount() {
Integer i = bookDao.tableCount();
return "这张表里一共有: " + i + " 条数据!";
}
/* 去测试类里跑通我们的代码 */
// 查询单个数据,表里共有多少条数据
String s = bookService.selectCount();
System.out.println(s);
// 这张表里一共有: 2 条数据!
可以看到,确实如此。
我们在什么情况下会用到这种情况呢,我们在商城买东西的时候,会展示图片和名称,在我们进行点击的时候,我们可以点击到对应的页面,看到物品的详细情况。根据id查询物品的详细信息,然后返回对象。
这是我们需要用到的方法,具有三个参数。
第一个参数是我们的sql语句,
第二个参数 RowMapper,它本身是一个接口,返回不同类型的数据,使用这个接口里面的实现类,可以完成数据的封装。
第三个参数是我们传递sql语句中的呢个问号占位符的值。
到这里就要注意了,我们必须保证Book类的字段属性要和数据库字段名称完全一致。这里我们重写Book类
/*BOOK*/
package com.w.spring5.entity;
public class Book {
private int user_id;
private String username;
private String ustatus;
public Book() {}
public Book(int user_id, String username, String ustatus) {
this.user_id = user_id;
this.username = username;
this.ustatus = ustatus;
}
public int getUser_id() {
return user_id;
}
public void setUser_id(int user_id) {
this.user_id = user_id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getUstatus() {
return ustatus;
}
public void setUstatus(String ustatus) {
this.ustatus = ustatus;
}
@Override
public String toString() {
return "Book{" +
"user_id=" + user_id +
", username='" + username + '\'' +
", ustatus='" + ustatus + '\'' +
'}';
}
}
因为我们要返回的是一个Book对象,所以我们的接口方法也要定义返回类型为 Book。
// 查询返回对象。 根据id返回对应的book对象。
Book selectReturnObj(int id);
接下来去写对应的实现类方法
@Override
public Book selectReturnObj(int id) {
String sql = "select * from t_book where user_id = ?";
return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Book.class), id);
}
这里三个参数都代表什么我有讲过,第二个为Spring帮你封装好的RowMapper对象。BeanPropertyRowMapper继承了RowMapper,里面的泛型里写什么就给你返回什么类型的对象。当然通过后面的括号里面给一个类的字节码也是可以的,如我上面的写法, Book.class, 第三个参数为 你要查询的 id。是几号。也就是sql的问号占位符占掉的地方。
我们在什么地方用的到他呢,我们查询图书列表的分页。也就是分页查询。用得到这个方法。
返回集合写法跟我们上面的返回对象类型写法差不多。我们在下面写一下。
这是我们需要用到的方法,具有三个参数。
第一个参数是我们的sql语句,
第二个参数 RowMapper,它本身是一个接口,返回不同类型的数据,使用这个接口里面的实现类,可以完成数据的封装。
第三个参数是我们传递sql语句中的呢个问号占位符的值。
会发现他跟我们的返回对象类型一模一样的参数。
因为我们是查询全部,所以不需要写参数。
还是老样子,我们先处理BookDao接口的编写
// 查询返回集合。
List<Book > selectReturnList();
然后 去实现对应的 Impl实现类
// 返回集合
@Override
public List<Book> selectReturnList() {
String sql = "select * from t_book";
List<Book> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Book.class));
return query;
}
可以看到,query方法,默认就是返回List集合。
然后我们去编写逻辑层的Service代码。我们在Service代码中,做了一层循环处理。
// 返回集合对象。
public void selectReturnListBook() {
List<Book> books = bookDao.selectReturnList();
System.out.println("此处为元数据: " + books + " ..");
System.out.println("");
System.out.println("一下数据是经过处理的数据: ");
ArrayList b = (ArrayList) books;
for (Object o : b) {
System.out.println(o);
}
}
现在来到测试类里得到的最终结果。
// 这里为调用方法
bookService.selectReturnListBook();
/*
此处为元数据: [Book{user_id=1, username='《算法导论》', ustatus='存在'}, Book{user_id=2, username='Java编程思想', ustatus='存在'}] ..
一下数据是经过处理的数据:
Book{user_id=1, username='《算法导论》', ustatus='存在'}
Book{user_id=2, username='Java编程思想', ustatus='存在'}
* */
如果我们需要对数据库进行增加,删除,修改的操作,我们需要调用 jdbcTemplate.update();方法,update里存在了两个参数,第一个是我们对应的SQL语句,第二个是不定长参数,用来替换掉Sql占位的地方。也就是传入值。
如果我们需要进行查询操作。我们就需要使用 jdbcTemplate.queryForObject, 在返回单一数据的时候,我们只需要传递两个参数即可,第一个是我们对应的sql语句,第二个参数为 我们返回的类型,String就写String.class,int就写Integer.class,等等。
如果我们需要查询返回对象结果,我们需要使用到三个参数,方法依然是jdbcTemplate.queryForObject, 第一个参数依然是我们的Sql语句,第二个参数是我们需要new一个对象。这个对象帮我们实现了返回值类型,new BeanPropertyRowMapper<>(Book.class),你给他泛型什么类型,他就会给你返回什么类型。第三个参数是你对应的占位符的值。
如果我们需要分页查询的场景,要返回集合类型的对象,只需要调用jdbcTemplate.query()方法即可。使用的方式和参数类型与上面不变,第三个参数不是必须要传递,上面也雷同。如果我们查询的是 select * from xxx; 这种不需要参数的,呢么我们写两个参数即可,sql语句和 new BeanPropertyRowMapper<>(Book.class), 他默认返回的就是一个List集合。
什么叫批量增删改,就是我可以同时向表中添加多条数据,修改多条数据,删除多条数据。
在jdbcTemplate模板中, 有一个方法,这个方法就可以实现批量功能,
batchUpdate(String sql, List
第一个参数: sql语句。
第二个参数,List集合。表示你添加的多条记录的数据。List集合中的泛型是Object[] 数组。
下面我们使用代码来实现一下
接口的编写
// 批量添加
void batchAddBook(List<Object[] > o);
实现类的编写
@Override
public void batchAddBook(List<Object[]> o) {
String sql = "insert into t_book values(?, ?, ?)";
int[] ints = jdbcTemplate.batchUpdate(sql, o);
System.out.println("共影响了: " + Arrays.toString(ints) + " 行");
}
Service的编写
// 添加多行数据
public void batchAddBook(List<Object[] > objects) {
bookDao.batchAddBook(objects);
}
测试代码结果
// 添加多行数据
List<Object[] > objects = new ArrayList<>();
Object[] objects1 = {3, "Java二十一天速成", "缺货"};
Object[] objects2 = {4, "C++从入门到精通", "缺货"};
Object[] objects3 = {5, "Spring实战", "爆卖"};
objects.add(objects1);
objects.add(objects2);
objects.add(objects3);
bookService.batchAddBook(objects);
// 共影响了: [1, 1, 1] 行
最终结果,数组为: [1, 1, 1] 是什么意思呢,就是没一次执行,都会让一条语句改变,我们数组里有三次,所以执行了三次,改变了三行语句。我们在最后看数据库的表。
确实进行了添加数据。说明我们的批量结果成功了。
其实剩下的修改和删除基本上是一样的,他们都跟添加差不多,调的方法都是相同的,唯一不同的是参数和sql语句。
我们依然来编写接口的方法 (PS: 写了千万遍,我快写吐辣~~~)
// 批量修改
void batchUpdateBook(List<Object[]> objects);
Impl 改变的只有sql和传入的参数
@Override
public void batchUpdateBook(List<Object[]> objects) {
String sql = "update t_book set username=?, ustatus=? where user_id=?";
int[] ints = jdbcTemplate.batchUpdate(sql, objects);
System.out.println(Arrays.toString(ints));
}
Service
// 批量修改
public void batchUpdate(List<Object[] > objects) {
bookDao.batchUpdateBook(objects);
}
Test
// 批量修改
List<Object[] > list = new ArrayList<>();
// 第一个位置对应 名称, 第二个位置对应 状态, 第三个对应id
Object[] objects1 = {"C++怎么可能精通", "有货", 4};
Object[] objects2 = {"SpringBoot项目实战", "有货", 5};
Object[] objects3 = {"Java怎么可能速成", "有货", 3};
list.add(objects1);
list.add(objects2);
list.add(objects3);
bookService.batchUpdate(list);
// [1, 1, 1]
抱歉,图片丢了。
上图为修改前
下图为修改后
确实修改成功了。
我不废话了啊,直接写代码了。感觉只要认真看了前面的,这里的不是问题。
/* interface */
// 批量删除 根据id去删除
void batchDeleteBook(List<Object[] > objects);
Impl
// 批量删除
@Override
public void batchDeleteBook(List<Object[]> objects) {
String sql = "delete from t_book where user_id = ?";
int[] ints = jdbcTemplate.batchUpdate(sql, objects);
System.out.println(Arrays.toString(ints));
}
Service
// 批量删除
public void batchDeleteBook(List<Object[] > objects) {
bookDao.batchDeleteBook(objects);
}
测试代码
// 批量删除
List<Object[] > list = new ArrayList<>();
Object[] o1 = {3};
Object[] o2 = {4};
Object[] o3 = {5};
list.add(o1);
list.add(o2);
list.add(o3);
bookService.batchDeleteBook(list);
// [1, 1, 1]
}
删除前
删除后
完结。
Interface —> BookDao
package com.w.spring5.dao;
import com.w.spring5.entity.Book;
import java.util.List;
public interface BookDao {
// 添加功能
void add(Book book);
// 修改功能
void update(Book book);
// 删除功能 delete 根据id删除对应的整行(数据库主键为id)
void delete(int id);
// 查询表中共有多少条记录
Integer tableCount();
// 查询返回对象。 根据id返回对应的book对象。
Book selectReturnObj(int id);
// 查询返回集合。
List<Book > selectReturnList();
// 批量添加
void batchAddBook(List<Object[] > o);
// 批量修改
void batchUpdateBook(List<Object[]> objects);
// 批量删除 根据id去删除
void batchDeleteBook(List<Object[] > objects);
}
Impl
package com.w.spring5.dao;
import com.w.spring5.entity.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.util.Arrays;
import java.util.List;
@Repository("bookDaoImpl")
public class BookDaoImpl implements BookDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void add(Book book) {
String sql = "delete from t_book where user_id = ?";
// Object[] o = {book.getBookId(), book.getBookName(), book.getBookActive()};
int update = jdbcTemplate.update(sql, book.getUser_id());
// System.out.println("共更新了一条数据,更新的内容为: " + book.toString() + ", 共更新了 "
// + update + " 条数据。");
System.out.println(update);
}
// 更新数据
@Override
public void update(Book book) {
/*
* 我们更新数据,分别更新了名称和状态,通过id进行更改。id为唯一主键,所以不用担心重复问题
* */
String sql = "update t_book set username=?, ustatus=? where user_id=?";
// 这里的数组顺序要对应上面的占位符顺序,否则要通过下标取。
Object[] o = {book.getUsername(), book.getUstatus(), book.getUser_id()};
int update = jdbcTemplate.update(sql, o);
System.out.println("执行更新数据操作成功,共执行: " + update + " 条, 更新的内容为: {"
+ book.getUsername() + ", 更新状态为: " + book.getUstatus() + "}");
}
// 删除数据
@Override
public void delete(int id) {
String sql = "delete from t_book where user_id = ?";
// 这里直接传入的就是一个数字,id,通过id删除对应的数据
int update = jdbcTemplate.update(sql, id);
System.out.println("删除数据成功, 共删除一条数据,数据的id为: " + id);
}
// 返回单个数据
@Override
public Integer tableCount() {
String sql = "select count(*) from t_book";
return jdbcTemplate.queryForObject(sql, Integer.class);
}
// 查询返回对象
@Override
public Book selectReturnObj(int id) {
String sql = "select * from t_book where user_id = ?";
return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Book.class), id);
}
// 返回集合
@Override
public List<Book> selectReturnList() {
String sql = "select * from t_book";
List<Book> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Book.class));
return query;
}
// 批量添加
@Override
public void batchAddBook(List<Object[]> o) {
String sql = "insert into t_book values(?, ?, ?)";
int[] ints = jdbcTemplate.batchUpdate(sql, o);
System.out.println("共影响了: " + Arrays.toString(ints) + " 行");
}
// 批量修改
@Override
public void batchUpdateBook(List<Object[]> objects) {
String sql = "update t_book set username=?, ustatus=? where user_id=?";
int[] ints = jdbcTemplate.batchUpdate(sql, objects);
System.out.println(Arrays.toString(ints));
}
// 批量删除
@Override
public void batchDeleteBook(List<Object[]> objects) {
String sql = "delete from t_book where user_id = ?";
int[] ints = jdbcTemplate.batchUpdate(sql, objects);
System.out.println(Arrays.toString(ints));
}
}
Service
package com.w.spring5.service;
import com.w.spring5.dao.BookDao;
import com.w.spring5.entity.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service("bookService")
public class BookService {
@Autowired
@Qualifier("bookDaoImpl")
private BookDao bookDao;
// 添加
public void addBook(Book book) {
bookDao.add(book);
}
// 更新
public void updateBook(Book book) {
bookDao.update(book);
}
// 删除
public void deleteBook(int id) {
bookDao.delete(id);
}
// 查询单个数据。也就是实现我们的查询表内有多少条数据
public String selectCount() {
Integer i = bookDao.tableCount();
return "这张表里一共有: " + i + " 条数据!";
}
// 查询返回对象。
public Book selectReturnObject(int id) {
return bookDao.selectReturnObj(id);
}
// 返回集合对象。
public void selectReturnListBook() {
List<Book> books = bookDao.selectReturnList();
System.out.println("此处为元数据: " + books + " ..");
System.out.println("");
System.out.println("一下数据是经过处理的数据: ");
ArrayList b = (ArrayList) books;
for (Object o : b) {
System.out.println(o);
}
}
// 添加多行数据
public void batchAddBook(List<Object[] > objects) {
bookDao.batchAddBook(objects);
}
// 批量修改
public void batchUpdate(List<Object[] > objects) {
bookDao.batchUpdateBook(objects);
}
// 批量删除
public void batchDeleteBook(List<Object[] > objects) {
bookDao.batchDeleteBook(objects);
}
}
测试类
import com.w.spring5.entity.Book;
import com.w.spring5.service.BookService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.ArrayList;
import java.util.List;
public class MyTest {
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
BookService bookService = context.getBean("bookService", BookService.class);
// 我们更新 id 为 2 的数据,把Java编程实现,更新为Java编程思想。状态从缺货,更为存在。
bookService.updateBook(new Book(2, "Java编程思想", "存在"));
// 执行更新数据操作成功,共执行: 1 条, 更新的内容为: {Java编程思想, 更新状态为: 存在}
// 我们删除id为三的数据
bookService.deleteBook(3);
// 删除数据成功, 共删除一条数据,数据的id为: 3
// 查询单个数据,表里共有多少条数据
String s = bookService.selectCount();
System.out.println(s);
System.out.println(bookService.selectReturnObject(1));
// Book{user_id=1, username='《算法导论》', ustatus='存在'}
System.out.println(bookService.selectReturnObject(2));
// Book{user_id=2, username='Java编程思想', ustatus='存在'}
bookService.selectReturnListBook();
/*
此处为元数据: [Book{user_id=1, username='《算法导论》', ustatus='存在'}, Book{user_id=2, username='Java编程思想', ustatus='存在'}] ..
一下数据是经过处理的数据:
Book{user_id=1, username='《算法导论》', ustatus='存在'}
Book{user_id=2, username='Java编程思想', ustatus='存在'}
* */
// 添加多行数据
List<Object[] > objects = new ArrayList<>();
Object[] objects1 = {3, "Java二十一天速成", "缺货"};
Object[] objects2 = {4, "C++从入门到精通", "缺货"};
Object[] objects3 = {5, "Spring实战", "爆卖"};
objects.add(objects1);
objects.add(objects2);
objects.add(objects3);
bookService.batchAddBook(objects);
// 共影响了: [1, 1, 1] 行
// 批量修改
List<Object[] > list1 = new ArrayList<>();
// 第一个位置对应 名称, 第二个位置对应 状态, 第三个对应id
Object[] objects11 = {"C++怎么可能精通", "有货", 4};
Object[] objects22 = {"SpringBoot项目实战", "有货", 5};
Object[] objects33 = {"Java怎么可能速成", "有货", 3};
list1.add(objects11);
list1.add(objects22);
list1.add(objects33);
bookService.batchUpdate(list1);
// [1, 1, 1]
// 批量删除
List<Object[] > list = new ArrayList<>();
Object[] o1 = {3};
Object[] o2 = {4};
Object[] o3 = {5};
list.add(o1);
list.add(o2);
list.add(o3);
bookService.batchDeleteBook(list);
// [1, 1, 1]
}
}
事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败,呢么所有的操作都会失败。
典型场景:
银行转账的场景。比如现在有两个人,一个叫张三,一个叫李四。现在的过程中,李四,想要转账100 元给 张三。
逻辑是什么呢, 李四,少100,张三,多100。在李四少完100后,张三才能多100。假设这个转账过程中,出现了异常,比如最典型的网络断开,李四,少了100,但是张三不会多。这种情况,就会引起异常。最终的结果就是,李四没有少,张三没有多。
所以就是,要么都成功,要么全失败。
(1)原子性
他指的就是,逻辑上一组操作,要么都成功,如果有一个失败,呢么所有的操作都会失败。
(2)一致性
操作之前和操作之后他的总量是不变的。举例子,张三和李四一人有100元,加在一起就是200元,然后张三转给李四100元,张三为0,李四为200,他们加在一起还是200,总数不变。
(3)隔离性
在多事务之间进行操作的时候,他们不会产生影响。
(4)持久性
在最后的数据,我们是要提交的,提交之后,表内的数据,真正的发生了变化。
我们来搭建一个银行转账的情况,来实现事务的环境。
已经学习到了Spring框架,呢么肯定学习了JavaEE的部分,JavaEE的三层架构,Web层,Service层,和Dao层。
Web层也可以叫做视图层,用来做前端部分,Service也可以叫做业务逻辑层。Dao也叫做数据持久层,我们的数据库一些操作就在这里完成。
现在我们按照这个结构,把环境做一个搭建。我们主要在Service层和Dao层。
转账的操作,一个少钱,一个多钱。
所以我们在Dao层,创建两个方法,一个少钱,一个多钱。
然后我们来到业务逻辑层,创建一个转账的方法。我们可以在转账的方法内,调用Dao的两个方法。
我们的第一步应该是什么?因为我们要操作数据库,所以应该是新建一个数据库。然后新建一个表。然后添加记录。
表的设计。数据库名称为: user_db, 表名称为: t_account,在表里插入两条数据
等等我们需要实现,张三,给李四转账 100 元。
接下来需要我们做的是,配置连接池,开启注解扫描支持,创建Dao,Service,并且注入对应的属性关系。完成对象的创建。
Service 注入 Dao。 Dao注入JdbcTemplate,在JdbcTemplate中注入DataSource
配置XML文件,开启注解扫描,配置连接池,创建JdbcTemplate对象。
<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 http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.w.spring5" />
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="jdbc:mysql:///user_db" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="username" value="root" />
<property name="password" value="root" />
bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
bean>
beans>
完成对象的创建以及属性的注入。
UserService
package com.w.spring5.service;
import com.w.spring5.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service("userService")
public class UserService {
@Autowired
@Qualifier("userDaoImpl")
private UserDao userDao;
}
UserDao
package com.w.spring5.dao;
public interface UserDao {
}
UserDaoImpl
package com.w.spring5.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository("userDaoImpl")
public class UserDaoImpl implements UserDao {
@Autowired
@Qualifier("jdbcTemplate")
private JdbcTemplate jdbcTemplate;
}
接下来写,具体的实现部分。在Dao里面有两个方法,一个少钱,一个多钱。在Service里调用这两个方法。实现对应的转账逻辑
我们要实现张三转100给李四。
Dao 和 Impl部分
Dao
package com.w.spring5.dao;
public interface UserDao {
// 增加钱的方法。
void addMoney();
// 减少钱的方法
void reduceMoney();
}
Impl
package com.w.spring5.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository("userDaoImpl")
public class UserDaoImpl implements UserDao {
@Autowired
@Qualifier("jdbcTemplate")
private JdbcTemplate jdbcTemplate;
// 我们要实现张三转100给李四。
// 少钱
@Override
public void reduceMoney() {
String sql = "update t_account set money = money-? where username = ?";
// 上面sql语句的意思是。更新 t_account表的数据, 设置 money 的值为 money 减去 参数1
// 设置的方式为 username 等于 张三的时候,执行前面的内容。
jdbcTemplate.update(sql, 100, "张三");
}
// 多钱
@Override
public void addMoney() {
String sql = "update t_account set money = money+? where username = ?";
// 只是改了个人名而已,具体看上面实现。
jdbcTemplate.update(sql, 100, "李四");
}
}
Service
package com.w.spring5.service;
import com.w.spring5.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service("userService")
public class UserService {
@Autowired
@Qualifier("userDaoImpl")
private UserDao userDao;
// 转账的方法
public void accountMoney() {
// 张三少一百
userDao.reduceMoney();
// 李四多100
userDao.addMoney();
}
}
这样,我们的环境搭建就已经完成了。最终我们做一个测试
import com.w.spring5.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.accountMoney();
}
}
在我们执行之后,他并不会给我们任何反馈(反正没报错)。我们这个时候去看表的信息,就会发现,张三的钱,变成了900,而李四的钱,变成了1100。
注意,这仅仅是环境搭建好了而已。
按照我们上面的转账代码,如果他在完全正常的情况下,他肯定是没有问题的。但是如果运行的过程中,代码产生了异常,就会出现问题、
我们在service中模拟一个异常
import com.w.spring5.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service("userService")
public class UserService {
@Autowired
@Qualifier("userDaoImpl")
private UserDao userDao;
// 转账的方法
public void accountMoney() {
// 张三少一百
userDao.reduceMoney();
// 模拟异常
int a = 10 / 0;
// 李四多100
userDao.addMoney();
}
}
在这里报异常之后,我们会执行张三少一百,但是李四多100,并不会增加。
我们现在数据库中,把双方的钱还原,张三-1000, 李四-1000
然后我们运行程序。得到以下结果
我们发现,张三的钱,扣掉了100元,但是李四并没有多出对应的钱,我们的钱从总数2000,变为了1900。按照真实的场景,在张三少完钱后,李四的钱没有多,这样会触发异常,导致回滚。张三的钱也不应该少。
呢么如何解决上述的问题呢,我们就需要使用事务来进行操作,要么全部成功,要么全部失败。
添加事务到JavaEE三层结构里面 Service 层 (业务逻辑层)
在Spring进行事务管理操作,有两种方式,第一种叫做编程时事务管理。第二种叫做声明式事务管理。(在实际开发中,我们用的较多的是声明式事务管理。)
我们会选择使用两种方式, 第一种是基于XML做到的。第二种则是基于注解方式实现的。注解方式是我们重点是用。因为简洁和方便。
基于注解实现
首先我们在Spring的配置文件中。配置事务管理器。 说是配置事务管理器,其实跟JdbcTemplate一样,只是把他的主接口给创建出来,然后塞到Bean容器中 并且在内容里注入数据源。
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
bean>
在Spring配置文件,开启事务注解。
在Spring的配置文件中,我们需要先引入一个命名空间,tx。用于做跟事务相关的东西。然后开启事务注解
transaction-manager表示,你要开启哪一个事务注解的部分,这里我们把前面配置的事务管理器给他传入。
<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"
xmlns:tx="http://www.springframework.org/schema/tx"
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/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="com.w.spring5" />
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" />
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="jdbc:mysql:///user_db" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="username" value="root" />
<property name= "password" value="root" />
bean>
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
bean>
beans>
然后我们需要在Service的类上面,或者在类方法里面,添加一个事务的注解。如果把它标注到类上面,就表示,这个类里的所有的方法,都添加上了事务。如果把这个注解添加到类里面的方法上面,他只会为你这个方法添加事务。
@Transactional
package com.w.spring5.service;
import com.w.spring5.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service("userService")
@Transactional // 这个注解可以加到类上,也可以加到方法上去。
public class UserService {
@Autowired
@Qualifier("userDaoImpl")
private UserDao userDao;
// 转账的方法
public void accountMoney() {
// 第一步 开启事务
// 第二步,进行业务操作
userDao.reduceMoney();
// 模拟异常
int a = 10 / 0;
// 李四多100
userDao.addMoney();
// 第三步,没有发生异常,提交事务。
}
}
在加上这个注解之后,我们在去测试类中重新跑一下代码,当然,在跑i前别忘了把张三的数据还原为1000;
他依然会给我们报错,和上一次的一样,但是这次打开表去观察数据,发现数据并没有发生改变,而是依然是1000 + 1000的结果。这就是事务。
在我们的注解@Transactional后面,在这个注解里可以配置针对事务的一些相关的参数。
现在我们用到的几个参数有
propagation: 事务的传播行为
什么是事务传播行为,假设我们现在有两个操作数据库的方法,一个叫add,一个叫update。 再假设。add上被标注了事务,而update却没有被标注事务。呢么我们要在add里调用update,应该怎么做。也有可能是两个方法都有事务,各种情况。
在Spring框架中,事务的传播行为共有七种。
REQUIRED: 我们假设在这里,add方法本身有自己所属的事务,在add方法里调用update方法。呢么 update方法,会使用当前 add方法里的事务。但是如果add方法本身没有事务,调用update方法之后,创建新的事务进行操作。
比较官方一点的说法(死板): 如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,在自己的事务内运行。
REQUIRED_NEW: 讲的通俗一点,我们还是假设add方法存在事务,update不存。我们在add内调用update方法,她不管你是否在add方法上存在事务,存在也好,不存在也好。他都会去给你新建事务。也就是说,add方法里,有事务,但我就是不用。我去给你新建,没有我也给你新建。
比较官方一点的说法(死板):当前的方法必须启动新的事务,并在他自己的事务内运行,如果有事务正在运行,应该将他挂起。
在Spring中有七种行为,这两种最常见剩余五种我一一列举,要记住前两个
SUPPORTS: 如果有事务在运行,当前的方法就在这个事务内运行,否则他可以不运行在事务中。
NOT_SUPPORTED : 当前的方法不应该运行在事务中,如果有运行的事务,就将他挂起。
**MANDATORY:**当前的方法必须运行在事务内部,如果没有正在运行的事务,则抛出异常。
NEVER:当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常。
**NESTED:**如果有事务在运行。当前的方法就应该在这个事务的嵌套事务内运行,否则,就启动一个新的事务,并在他自己的事务内运行。
如何去使用这个传播行为,我们在注解的属性上加上 @Transactional(propagation = Propagation.REQUIRED)
也就是加上一个 propagation = Propagation.行为
我们如果不写的话,它默认就是上面呢个REQUIRED。
ioslation: 事务的隔离级别
(1) 事务有个特性,称为隔离性 。多事务操作之间,他们不会产生影响。
不考虑隔离性,会产生一系列的问题。总结起来有这么几个读的问题。
脏读,不可重复读,虚读/幻读。
脏读: 一个未提交的事务,读取到了另一个未提交的事务的数据。这是一个致命问题。
不可重复读:一个未提交的事务,读取到了另一个提交事务中的修改的数据。这是一种现象。
虚度:一个未提交的事务,读取到了另一个提交事务添加数据。
但是问题已经出现了,要怎么解决呢,就用到了事务的隔离性。就可以解决这几个问题。通过设置事务的隔离性,就能解决这三个问题。
事务的隔离级别
脏读 | 不可重复读 | 幻读 | |
---|---|---|---|
READ UNCOMMITTED (读未提交) | 有 | 有 | 有 |
READ COMMITTED (读已提交) | 无 | 有 | 有 |
REPEATABLE READ(可重复读) | 无 | 无 | 有 |
SERIALIZABLE (串行化) | 无 | 无 | 无 |
具体操作我们只需要在注解中添加属性, isolation = Isolation.SERIALIZABLE, lsolation后面可以跟上面的四个值。来解决对应的操作。
我们使用的Mysql默认的级别,就是可重复读。
timeout: 超时时间
这个很好理解,我们在创建事务之后,会有一个提交的操作,我们规定在多长时间内进行事务提交,如果这段时间内没有提交,呢么就进行事务回滚。总结下来就是,事务需要在一定的时间内进行提交,如果不提交,呢么就要做事务的回滚。
在注解中,属性的默认值为: -1 ,也就是无超市。我们可以进行设置,设置是以秒为单位,进行计算。
timeout = 20 表示二十秒内,如果没有进行事务提交,就进行事务回滚。
readOnly: 是否只读
读: 查询操作。写: 添加,修改,删除操作。
reanOnly默认值是 false。 表示你可以查询,也可以进行增加,修改,删除。如果改为true,呢么就只能进行查询操作。
readOnly = false; 默认值。
rollbackFor: 回滚
设置出现哪一些异常进行事务回滚。
noRollbackFor: 不回滚
设置出现哪一些异常不进行回滚。
我们在先前配置AOP的时候,完全注解开发,是要先配置一个配置类,然后在配置类内中进行配置。让他代替XML文件。并且先对德鲁伊连接池进行配置。
package com.w.spring5.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration // 代表这是一个配置类
@ComponentScan(basePackages = {"com.w.spring5"}) // 开启注解扫描
@EnableTransactionManagement // 开启我们的事务
public class TxConfig {
// 创建数据库的连接池
@Bean
public DruidDataSource getDruidDataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName("com.mysql.jdbc.Driver"); // 驱动名称
druidDataSource.setUrl("jdbc:mysql:///user_db"); // 数据库地址
druidDataSource.setUsername("root"); // 数据库用户名
druidDataSource.setPassword("root"); // 数据库密码
return druidDataSource;
}
}
通过注解创建Bean容器,其实也是很简单的,只需要在方法上面添加@Bean。然后在方法里面return回去一个对应的对象。即可。然后通过set方法,进行设置值。 具体的方法对应具体的值在代码中的注释有解释。
// 我们还需要创建JdbcTemplate模板对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
// 到IOC容器中,根据类型找到DataSource。
return new JdbcTemplate(dataSource);
}
我们要使用IOC容器中的对象,在方法内进行一个参数的书写,这个参数会在IOC容器中根据类型自动注入。因为DataSource是Java提供的API,阿里巴巴提供的DruidDataSource实现了DataSource,也就是他是这个接口的子接口,我们可以直接根据类型注入进去。
我们开启事务的支持。
// 创建事务管理器
@Bean
public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource) {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
原理跟JdbcTemplate差不多。好好读一下代码。
然后我们进行测试类的书写,我在这里出了个错误,错误是创建UserDaoImpl为Bean容器时失败,原因是jdbcTemplate注入属性时失败。原因是我在上面进行了指定名称注入,所以。大家要把他对应的代码注解给删掉。然后还要删除掉UserService里的手动异常,整数除以0.这样代码就可以跑起来。张三少100,李四多100;
import com.w.spring5.config.TxConfig;
import com.w.spring5.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MyTest {
@Test
public void test() {
ApplicationContext context = new AnnotationConfigApplicationContext(TxConfig.class);
UserService userService = context.getBean("userService", UserService.class);
userService.accountMoney();
}
}
执行成功,张三少100,李四多100。
完整的配置类代码。
package com.w.spring5.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@Configuration // 代表这是一个配置类
@ComponentScan(basePackages = {"com.w.spring5"}) // 开启注解扫描
@EnableTransactionManagement // 开启我们的事务
public class TxConfig {
// 创建数据库的连接池
@Bean
public DruidDataSource getDruidDataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName("com.mysql.jdbc.Driver"); // 驱动名称
druidDataSource.setUrl("jdbc:mysql:///user_db"); // 数据库地址
druidDataSource.setUsername("root"); // 数据库用户名
druidDataSource.setPassword("root"); // 数据库密码
return druidDataSource;
}
// 我们还需要创建JdbcTemplate模板对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
// 到IOC容器中,根据类型找到DataSource。
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
// 创建事务管理器
@Bean
public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource) {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}