spring框架
一、框架
1、框架是一个半成品
封装好了一些代码,不需要你写了,你直接可以使用。
2、框架是一种开发规范
每个人都有自己的开发习惯,但是我们进行团队开发,需要统一开发规范。
3、框架可以提高开发效率
项目的结构和基础都由框架提供,我们直接编写业务需求。
4、解耦
二、spring框架
1、spring是Java开发的核心框架
每个公司都会用。
springboot是spring框架的简化框架。
2、spring是全方位的JavaEE解决方案。
spring框架提供了JavaEe开发所需要的所有东西。
3、spring全家桶
spring核心框架,springMVC,spring Data,spring security,spring boot,springCloud==
4、spring框架的核心功能
IOC:bean工厂,spring容器。
AOP:面向切面编程(底层使用动态代理)。
spring jdbc:连接关系型数据库。
事务管理:aop的体现。
整合其他框架。
5、spring有两种配置形式
xml形式
注解
三、项目中导入spring框架
1、至少需要如下jar包
spring-core
spring-context
spring-beans
spring-aop(如果不使用注解,可以不引入)
spring-expressEl
logging
这是核心包,使用spring框架必须引入的包。
如果你要使用spring的其他功能,在此基础上引入相应的jar包。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vrL6VlPM-1632315227620)(spring框架.assets/image-20210915171326224.png)]
2、创建spring配置文件
src/main/resources: 配置文件名随便起,一般喜欢叫applicationContext.xml
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8oEcQkwn-1632315227622)(spring框架.assets/image-20210915171620078.png)]
四、控制反转IOC
1、控制反转
创建对象的权力交给spring容器。
由spring容器帮助我们创建好对象,然后将对象存在spring容器中管理。
如果我们想要使用这些对象,直接从spring容器中获取对象。
controller、service、dao一般都放入spring容器中,实体类和工具类一般不放。
2、编写一个类
package com.hs;
public class User {
private int id;
private String name;
private String username;
private String passwd;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
}
3、配置bean
applicationContext.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">
<!--将com.hs.User交给spring容器创建和管理-->
<!--默认bean对象是单例的-->
<!--id和name作用相同,一般使用id-->
<!--class是要实例化的类,包名.类名-->
<bean id="u1" name="u1" class="com.hs.User"></bean>
</beans>
4、获取对象
从spring容器中获取对象。
获取对象的方式:
(1)BeanFactory
(2)ApplicationContext:ClasspathXmlApplicationContext(推荐)
(3)ApplicationContext:FileSystemXmlApplicationContext
package com.hs;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
// 1、初始化容器
// 2、读取applicationContext.xml
// 3、创建bean
ApplicationContext context=new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
// 获取bean(默认是单例的)
User u1 = (User)context.getBean("u1");
User u2 = (User)context.getBean("u1");
System.out.println(u1.getId());
System.out.println(u1==u2); //true
}
}
五、依赖注入DI
1、依赖注入
a对象依赖了b对象,那么spring容器可以将b对象赋给a对象的属性。
2、注入的前提条件
a对象和b对象必须由spring容器创建和管理。
3、案例
package com.hs;
public class User {
private int id;
private String name;
private String username;
private String passwd;
// 依赖注入
private Company company;
public Company getCompany() {
return company;
}
public void setCompany(Company company) {
this.company = company;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
}
package com.hs;
public class Company {
private int cid;
private int cname;
public int getCid() {
return cid;
}
public void setCid(int cid) {
this.cid = cid;
}
public int getCname() {
return cname;
}
public void setCname(int cname) {
this.cname = cname;
}
}
<?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">
<!--将com.hs.User交给spring容器创建和管理-->
<!--默认bean对象是单例的-->
<!--id和name作用相同,一般使用id-->
<!--class是要实例化的类,包名.类名-->
<!--property:ref 注入指定id的对象 -->
<bean id="u1" name="u1" class="com.hs.User">
<property name="company" ref="cm"></property>
</bean>
<bean id="cm" class="com.hs.Company"></bean>
</beans>
package com.hs;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
// 1、初始化容器
// 2、读取applicationContext.xml
// 3、创建bean
ApplicationContext context=new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
// 获取bean
User u1 = (User)context.getBean("u1");
User u2 = (User)context.getBean("u1");
// System.out.println(u1.getId());
// System.out.println(u1==u2);
System.out.println(u1.getCompany().getCid());
}
}
4、常用的地方
Controller依赖service;
Service依赖Dao;
Dao依赖DBUtil;
5、IOC和DI是同一概念
都是spring容器管理bean。只是侧重点和描述同一事物的角度不同,ioc从创建的角度描述,di是对象之间的关系角度描述的。
六、bean配置信息详解
1、常用配置
id 对象的编号(唯一),我们需要通过它找到spring容器的对象
name 对象的名称(唯一,可以有多个名字),我们需要通过它找到spring容器的对象
class 要管理的类,spring容器会创建该类的对象(反射)
2、scope
bean的作用范围:
singleton 单例(默认)
prototype 多例,每次获取都会返回一个新对象
request 一次请求中,多次获取的都是同一个对象
session 一次会话,多次获取的都是同一个对象
3、生命周期的方法:
init-method="init方法名"
destroy-method="destroy"
4、单例的饿汉和懒汉
lazy-init
默认false,采用饿汉模式
true,采用懒汉模式。
七、属性注入方式
1、setter方法注入
(1)必须为属性提供setter方法
(2)在bean标签中使用
<property name="属性名" ref="被注入的对象id"></property>
package com.hs;
public class User {
private int id;
private String name;
private String username;
private String passwd;
// 依赖注入
private Company company;
public Company getCompany() {
return company;
}
public void setCompany(Company company) {
this.company = company;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
}
package com.hs;
public class Company {
private int cid;
private int cname;
public void init(){
System.out.println("初始化");
}
public void destroy(){
System.out.println("销毁");
}
public int getCid() {
return cid;
}
public void setCid(int cid) {
this.cid = cid;
}
public int getCname() {
return cname;
}
public void setCname(int cname) {
this.cname = cname;
}
}
<?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">
<!--将com.hs.User交给spring容器创建和管理-->
<!--默认bean对象是单例的-->
<!--id和name作用相同,一般使用id-->
<!--class是要实例化的类,包名.类名-->
<bean id="u1" name="u1" class="com.hs.User" scope="prototype" >
<property name="company" ref="cm"></property>
</bean>
<bean id="cm" class="com.hs.Company" scope="singleton" init-method="init" destroy-method="destroy" lazy-init="true"></bean>
</beans>
2、构造器注入
package com.hs;
public class Student {
public int id;
public String name;
public Clazz clazz;
public Student(int id, String name, Clazz clazz) {
this.id = id;
this.name = name;
this.clazz = clazz;
}
}
package com.hs;
public class Clazz {
private int cid;
private String cname;
public int getCid() {
return cid;
}
public void setCid(int cid) {
this.cid = cid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
}
<!-- 构造器注入-->
<bean id="clazz" class="com.hs.Clazz"></bean>
<bean id="student" class="com.hs.Student">
<constructor-arg index="0" value="1001"></constructor-arg>
<constructor-arg index="1" value="张三"></constructor-arg>
<constructor-arg index="2" ref="clazz"></constructor-arg>
</bean>
3、p空间注入
还是采用setter方法注入
只是简化的写法。
package com.hs;
public class Emp {
private int id;
private String name;
private Dept dept;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Dept getDept() {
return dept;
}
public void setDept(Dept dept) {
this.dept = dept;
}
}
package com.hs;
public class Dept {
private int did;
private String dname;
public int getDid() {
return did;
}
public void setDid(int did) {
this.did = did;
}
public String getDname() {
return dname;
}
public void setDname(String dname) {
this.dname = dname;
}
}
第一步,引用p空间
<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">
第二步,使用p空间
<!-- p空间注入-->
<bean id="dept" class="com.hs.Dept"></bean>
<bean id="emp" class="com.hs.Emp" p:id="9001" p:name="李萧" p:dept-ref="dept"></bean>
4、字段注入(注解)
注解
@AutoWired
@Resource
八、属性装配
使用注入的方式给属性赋值
1、简单类型属性
byte short int long
char boolean String
2、复杂类型
数组、List、Set、Map
3、对象
package com.hs;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class Wired {
private int aa;
private double bb;
private char cc;
private boolean dd;
private String ee;
private String[] ff;
private List<String> gg;
private Set<String> hh;
private Map<String,String> ii;
private HS hs;
public HS getHs() {
return hs;
}
public void setHs(HS hs) {
this.hs = hs;
}
public int getAa() {
return aa;
}
public void setAa(int aa) {
this.aa = aa;
}
public double getBb() {
return bb;
}
public void setBb(double bb) {
this.bb = bb;
}
public char getCc() {
return cc;
}
public void setCc(char cc) {
this.cc = cc;
}
public boolean isDd() {
return dd;
}
public void setDd(boolean dd) {
this.dd = dd;
}
public String getEe() {
return ee;
}
public void setEe(String ee) {
this.ee = ee;
}
public String[] getFf() {
return ff;
}
public void setFf(String[] ff) {
this.ff = ff;
}
public List<String> getGg() {
return gg;
}
public void setGg(List<String> gg) {
this.gg = gg;
}
public Set<String> getHh() {
return hh;
}
public void setHh(Set<String> hh) {
this.hh = hh;
}
public Map<String, String> getIi() {
return ii;
}
public void setIi(Map<String, String> ii) {
this.ii = ii;
}
}
package com.hs;
public class HS {
private int hid;
private String hname;
public int getHid() {
return hid;
}
public void setHid(int hid) {
this.hid = hid;
}
public String getHname() {
return hname;
}
public void setHname(String hname) {
this.hname = hname;
}
}
<bean id="wired" class="com.hs.Wired">
<property name="aa" value="1001"></property>
<property name="bb" value="12.5"></property>
<property name="cc" value="a"></property>
<property name="dd" value="true"></property>
<property name="ee" value="abcdefg"></property>
<property name="ff">
<array>
<value>11</value>
<value>12</value>
<value>13</value>
<value>14</value>
</array>
</property>
<property name="gg" >
<list>
<value>21</value>
<value>22</value>
<value>23</value>
</list>
</property>
<property name="hh" >
<set>
<value>31</value>
<value>32</value>
<value>33</value>
</set>
</property>
<property name="ii" >
<map>
<entry key="41" value="41"></entry>
<entry key="42" value="42"></entry>
<entry key="43" value="43"></entry>
</map>
</property>
<property name="hs" ref="hs"></property>
</bean>
<bean id="hs" class="com.hs.HS"></bean>
九、spring-web
1、Java项目中封装工具类
package com.hs.util;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringUtil {
private static ApplicationContext context;
static{
context=new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
}
public static <T> T getBean(String id){
return (T)context.getBean(id);
}
}
2、web项目开发
依然可以使用这个工具类来获取spring容器中的对象。
有没有更好的解决方案呢?
我们在启动tomat时,交给tomcat完成spring容器的初始化。
原理:
利用了上下文监听器实现。
当启动tomcat,tomcat创建一上下文对象,监听器对其处理:
初始化一个spring容器,放入上下文对象存储 。
实现:
package com.hs.web;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
@WebListener
public class SpringServletContextListener implements ServletContextListener {
ClassPathXmlApplicationContext context;
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
//初始化spring容器
context=new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
//放入上下文对象中
servletContextEvent.getServletContext().setAttribute("spring",context);
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
// 移除上下文
servletContextEvent.getServletContext().removeAttribute("spring");
// 销毁spring容器
context.close();
}
}
3、spring-web
刚才写的代码在spring-web中实现好了。
就不需要我们实现了。
(1)引入spring-web依赖
<!-- spring-web-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.3.14.RELEASE</version>
</dependency>
(2)web.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>
4、spring容器主要用来管理哪些bean
主要用于管理controller,service,dao
问题:
tomcat接受请求,根据url找servlet。
tomat去servlet容器中找,不会去spring容器中找。
如果想要tomcat去spring容器中找,使用springmvc框架。
十、多配置文件
1、配置文件拆分
所有信息配置到一个xml中,内容过长,不方便阅读。
可以对配置信息进行分类:
applicationContext-dao.xml 配置dao
applicationContext-service.xml 配置service
applicationContext-controller.xml 配置controller
applicationContext-base.xml 配置基本信息
2、Java项目
ApplicationContext context=new ClassPathXmlApplicationContext("classpath*:applicationContext*.xml");
3、web项目
<!-- spring配置文件的路径-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext*.xml</param-value>
</context-param>
十一、IOC注解形式
1、@Component注解
配置一个bean,交给spring容器托管。
比如:
@Component(“clz”)
等价:
package com.hs.domain;
import org.springframework.stereotype.Component;
@Component("clz")
public class Clazz {
private int cid;
private String cname;
public int getCid() {
return cid;
}
public void setCid(int cid) {
this.cid = cid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
}
2、配置注解扫描
<?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
http://www.springframework.org/schema/context/spring-context.xsd
">
<!--注解扫描-->
<context:component-scan base-package="com.hs"></context:component-scan>
</beans>
3、测试
package com.hs.test;
import com.hs.domain.Clazz;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Component;
public class TestDemo {
@Test
public void test1(){
// spring容器的初始化
ApplicationContext context=new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
// 获取bean
Clazz clz=(Clazz) context.getBean("clz");
System.out.println(clz);
}
}
4、其他bean注解
@Component有三个常用的子类:
@Controller controller bean
@Service service bean
@Repository dao bean
5、依赖注入
(1)@Autowired
按类型装配
(2)@Resource
按名字装配
package com.hs.domain;
import org.springframework.stereotype.Component;
@Component("clz")
public class Clazz {
private int cid;
private String cname;
public int getCid() {
return cid;
}
public void setCid(int cid) {
this.cid = cid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
}
十二、登录案例
1、登录的页面
<%--
Created by IntelliJ IDEA.
User: HS
Date: 2021/9/16
Time: 16:00
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="/login" method="get">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="passwd"><br>
<input type="submit" value="登录">
</form>
</body>
</html>
2、domain
package com.hs.domain;
public class Emp {
private int e_id;
private String e_name;
private String e_sex;
private String e_tel;
private String username;
private String passwd;
public int getE_id() {
return e_id;
}
public void setE_id(int e_id) {
this.e_id = e_id;
}
public String getE_name() {
return e_name;
}
public void setE_name(String e_name) {
this.e_name = e_name;
}
public String getE_sex() {
return e_sex;
}
public void setE_sex(String e_sex) {
this.e_sex = e_sex;
}
public String getE_tel() {
return e_tel;
}
public void setE_tel(String e_tel) {
this.e_tel = e_tel;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
}
3、DBUtil
略
4、Dao
package com.hs.dao.impl;
import com.hs.dao.EmpDao;
import com.hs.domain.Emp;
import com.hs.util.DBUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.sql.ResultSet;
import java.sql.SQLException;
@Repository
public class EmpDaoImpl implements EmpDao {
@Autowired
private DBUtil dbUtil;
@Override
public Emp findEmpByUsername(String username) {
String sql="select * from emp where username=?";
Object[] objs={username};
ResultSet rs = dbUtil.select(sql, objs);
Emp emp=null;
try {
if(rs.next()){
emp=new Emp();
emp.setE_id(rs.getInt("e_id"));
emp.setE_name(rs.getString("e_name"));
emp.setE_sex(rs.getString("e_sex"));
emp.setE_tel(rs.getString("e_tel"));
emp.setUsername(rs.getString("username"));
emp.setPasswd(rs.getString("passwd"));
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
dbUtil.close();
}
return emp;
}
}
5、Service
package com.hs.service.impl;
import com.hs.dao.EmpDao;
import com.hs.domain.Emp;
import com.hs.service.EmpService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class EmpServiceImpl implements EmpService {
@Autowired
private EmpDao empDao;
@Override
public int login(String username, String passwd) {
Emp emp = empDao.findEmpByUsername(username);
if(emp==null){
return 0;
}
if(emp.getPasswd().equals(passwd)){
return 2;
}
return 1;
}
}
6、pom.xml
<?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.hs</groupId>
<artifactId>springDemo02</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>springDemo02 Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.14.RELEASE</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework</groupId>-->
<!-- <artifactId>spring-web</artifactId>-->
<!-- <version>4.3.14.RELEASE</version>-->
<!-- </dependency>-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
</dependencies>
<build>
<finalName>springDemo02</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
7、springUtil
package com.hs.util;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringUtil {
private static ApplicationContext context;
static{
context=new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
}
public static <T> T getBean(String id){
return (T)context.getBean(id);
}
}
8、Controller
package com.hs.controller;
import com.hs.service.EmpService;
import com.hs.util.SpringUtil;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/login")
//对象保存在tomcat的servlet容器
public class EmpController extends HttpServlet {
//不能注入,因为EmpController在tomcat容器中,不放入spring容器中
private EmpService empService;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1
String username = req.getParameter("username");
String passwd = req.getParameter("passwd");
//2
empService=SpringUtil.getBean("empService");
int login = empService.login(username, passwd);
//3
if(login==0){
resp.sendRedirect("/login.jsp");
}
if(login==1){
resp.sendRedirect("/login.jsp");
}
if(login==2){
resp.sendRedirect("/index.jsp");
}
}
}
十三、AOP
1、动态代理
jdk动态代理:目标对象需要接口
cglib动态代理:目标对象不需要接口
2、AOP
面向切面编程,是对OOP的补充。
底层采用的就是动态代理实现的。
如果目标对象没有实现接口,采用cglib动态代理。
如果目标对象实现接口,采用jdk动态代理。
3、AOP原理(日志管理)
目标接口1:
package com.hs.service.impl;
public interface ILoginService {
void add();
void delete();
void update();
void select();
}
目标实现类1:
package com.hs.service.impl;
import java.util.Date;
public class LogService implements ILoginService{
//Log log=new Log();
public void add(){
//log.before();
System.out.println("add");
//log.after();
}
public void delete(){
//log.before();
System.out.println("delete");
//log.after();
}
public void update(){
//log.before();
System.out.println("update");
//log.after();
}
public void select(){
//log.before();
System.out.println("select");
//log.after();
}
}
目标类2:
package com.hs.service.impl;
public class LogService2 {
//Log log=new Log();
public void add(){
//log.before();
System.out.println("add");
//log.after();
}
public void delete(){
//log.before();
System.out.println("delete");
//log.after();
}
public void update(){
//log.before();
System.out.println("update");
//log.after();
}
public void select(){
//log.before();
System.out.println("select");
//log.after();
}
}
日志打印类:
package com.hs.service.impl;
import java.util.Date;
public class Log {
public void before(){
System.out.println("开始执行:"+new Date());
}
public void after(){
System.out.println("执行结束:"+new Date());
}
}
jdk代理工具类:
package com.hs.service.impl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JDK implements InvocationHandler {
private Object target;
private Log log=new Log();
public JDK(Object target){
this.target=target;
}
// 目标方法的增强
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("jdk:");
log.before();
Object invoke = method.invoke(target, args);
log.after();
return invoke;
}
// 获取代理对象
public <T> T getProxy(){
return (T)Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
}
cglib代理工具类:需要导入cglib依赖
package com.hs.service.impl;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
public class CgLib implements MethodInterceptor {
private Object target;
private Log log=new Log();
public CgLib(Object target){
this.target=target;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib:");
log.before();
Object invoke = method.invoke(target, objects);
log.after();
return invoke;
}
// 目标方法的增强
public <T> T getProxy(){
Enhancer en=new Enhancer();
en.setCallback(this);
en.setSuperclass(target.getClass());
return (T)en.create();
}
}
获取代理对象:根据判断选择代理模式
package com.hs.service.impl;
public class AOPDemo {
public static Object getProxy(Object target){
Class<?>[] interfaces = target.getClass().getInterfaces();
if(interfaces.length==0){
//cglib
CgLib cgLib=new CgLib(target);
//proxy
return cgLib.getProxy();
}else{
//jdk
JDK jdk=new JDK(target);
return jdk.getProxy();
}
}
}
测试类:
package com.hs.service.impl;
public class TestAOP {
public static void main(String[] args) {
LogService ls1=new LogService();
LogService2 ls2=new LogService2();
ILoginService poxy1=(ILoginService)AOPDemo.getProxy(ls1);
LogService2 poxy2=(LogService2)AOPDemo.getProxy(ls2);
poxy1.add();
poxy1.delete();
poxy1.update();
poxy1.select();
poxy2.add();
poxy2.delete();
poxy2.update();
poxy2.select();
}
}
4、spring-aop概念
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wQ3hgkkN-1632315227626)(spring框架.assets/image-20210916172515966.png)]
切面:通知+切点
通知:切面类中定义的用于增强的方法before、after
连接点:被增强的目标对象的方法。add,delete,update,select
切点:它是一个描述,对哪些连接点进行增强。
5、spring -aop实现方案
(1)spring aop技术
(2)aspectj技术(推荐)
6、spring - Aop实现
(1)导入依赖
spring-aop
cglib
aopalliance-1.0.jar
aspectj
aspectjweaver.jar
<?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.hs</groupId>
<artifactId>springDemo02</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>springDemo02 Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.14.RELEASE</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework</groupId>-->
<!-- <artifactId>spring-web</artifactId>-->
<!-- <version>4.3.14.RELEASE</version>-->
<!-- </dependency>-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.0</version>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.0</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<finalName>springDemo02</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
(2)目标类
package com.hs.service.impl;
public interface ILoginService {
void add();
void delete();
void update();
void select();
}
package com.hs.service.impl;
import java.util.Date;
public class LogService implements ILoginService{
//Log log=new Log();
public void add(){
//log.before();
System.out.println("add");
//log.after();
}
public void delete(){
//log.before();
System.out.println("delete");
//log.after();
}
public void update(){
//log.before();
System.out.println("update");
//log.after();
}
public void select(){
//log.before();
System.out.println("select");
//log.after();
}
}
package com.hs.service.impl;
public class LogService2 {
//Log log=new Log();
public void add(){
//log.before();
System.out.println("add");
//log.after();
}
public void delete(){
//log.before();
System.out.println("delete");
//log.after();
}
public void update(){
//log.before();
System.out.println("update");
//log.after();
}
public void select(){
//log.before();
System.out.println("select");
//log.after();
}
}
(3)编写切面类
public class Log {
public void before(){
System.out.println("开始执行:"+new Date());
}
public void after(){
System.out.println("执行结束:"+new Date());
}
}
(4)配置aop
<!-- 1、目标类-->
<bean id="ls1" class="com.hs.service.impl.LogService"></bean>
<bean id="ls2" class="com.hs.service.impl.LogService2"></bean>
<!-- 2、切面类-->
<bean id="log" class="com.hs.service.impl.Log"></bean>
<!-- 3、aop配置-->
<aop:config>
<!-- 切面-->
<aop:aspect id="litong" ref="log">
<!-- 切点:定义对谁增强的描述-->
<aop:pointcut id="logCut" expression="execution(public void com.hs.service.impl.*.*(..))"/>
<!-- 通知:-->
<aop:before method="before" pointcut-ref="logCut"></aop:before>
<aop:after method="after" pointcut-ref="logCut"></aop:after>
</aop:aspect>
</aop:config>
(5)测试
@Test
public void testAop(){
ApplicationContext context=new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
ILoginService ls1 = (ILoginService) context.getBean("ls1");
LogService2 ls2 = (LogService2) context.getBean("ls2");
ls1.add();
ls2.add();
}
7、切点表达式
(1)切点用于选择给哪些连接点做增强。
<aop:pointcut id=“切点名称” expression=“execution(选择的方法)”/>
(2)切点表达式
精准匹配
execution(public void com.hs.UserService.add(int,String))
参数模糊匹配
execution(public void com.hs.UserService.add(…))
方法名模糊匹配
execution(public void com.hs.UserService.*(…))
类名模糊匹配
execution(public void com.hs. * . * (…))
包名模糊匹配
execution(public void *. * . * (…))
返回值类型模糊匹配
execution(public * *. * . * (…))
修饰符模糊匹配
execution(* * *. * . * (…))
execution(* (…))
8、通知类型
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjtools -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.8.0</version>
</dependency>
(1)前置通知before
在目标方法之前织入。
可以没有参数,也可以有参数,比如JoinPoint。
(2)后置通知after-retuning
在目标方法之后织入。
(3)环绕通知around
(4)最终通知after
(5)异常通知after-throwing
package com.hs.service.impl;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import java.util.Date;
public class Log {
public void before(JoinPoint jp){
System.out.println(jp.getSignature().getName());
System.out.println("开始执行:"+new Date());
}
public void after(JoinPoint jp){
System.out.println(jp.getSignature().getName());
System.out.println("执行结束:"+new Date());
}
public void afterReturning(JoinPoint jp){
System.out.println(jp.getSignature().getName());
System.out.println("后置通知执行结束:"+new Date());
}
public void round(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕执行开始:"+new Date());
Object result=jp.proceed();//执行目标方法
System.out.println("执行结果是:"+result);
System.out.println("环绕执行结束:"+new Date());
}
public void exception(JoinPoint jp,Throwable e){
System.out.println("方法名:"+jp.getSignature().getName());
System.out.println(e);
System.out.println("异常通知");
}
}
<aop:config>
<!-- 切面-->
<aop:aspect id="litong" ref="log">
<!-- 切点:定义对谁增强的描述-->
<aop:pointcut id="logCut" expression="execution(public void com.hs.service.impl.*.*(..))"/>
<!-- 通知:-->
<aop:before method="before" pointcut-ref="logCut"></aop:before>
<aop:after method="after" pointcut-ref="logCut"></aop:after>
<aop:around method="round" pointcut-ref="logCut"></aop:around>
<aop:after-throwing throwing="e" method="exception" pointcut-ref="logCut"></aop:after-throwing>
<aop:after-returning method="afterReturning" pointcut-ref="logCut"></aop:after-returning>
</aop:aspect>
</aop:config>
通知执行顺序:
前置通知
异常通知
后置通知
最终通知
环绕通知和配置的先后顺序有关!!!
十四、AOP注解形式
1、bean的配置
开启注解扫描:
<!--注解扫描-->
<context:component-scan base-package="com.hs"></context:component-scan>
配置目标对象:
package com.hs.service.impl;
public interface ILoginService {
void add();
void delete();
void update();
void select();
}
package com.hs.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
public class LogService implements ILoginService{
//Log log=new Log();
public void add(){
int i=0;
// System.out.println(10/i);
//log.before();
System.out.println("add");
//log.after();
}
public void delete(){
//log.before();
System.out.println("delete");
//log.after();
}
public void update(){
//log.before();
System.out.println("update");
//log.after();
}
public void select(){
//log.before();
System.out.println("select");
//log.after();
}
}
package com.hs.service.impl;
import org.springframework.stereotype.Service;
@Service
public class LogService2 {
//Log log=new Log();
public void add(){
//log.before();
System.out.println("add");
//log.after();
}
public void delete(){
//log.before();
System.out.println("delete");
//log.after();
}
public void update(){
//log.before();
System.out.println("update");
//log.after();
}
public void select(){
//log.before();
System.out.println("select");
//log.after();
}
}
切面类:
package com.hs.service.impl;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class Log {
public void before(JoinPoint jp){
System.out.println(jp.getSignature().getName());
System.out.println("开始执行:"+new Date());
}
public void after(JoinPoint jp){
System.out.println(jp.getSignature().getName());
System.out.println("执行结束:"+new Date());
}
public void afterReturning(JoinPoint jp){
System.out.println(jp.getSignature().getName());
System.out.println("后置通知执行结束:"+new Date());
}
public void round(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕执行开始:"+new Date());
Object result=jp.proceed();//执行目标方法
System.out.println("执行结果是:"+result);
System.out.println("环绕执行结束:"+new Date());
}
public void exception(JoinPoint jp,Throwable e){
System.out.println("方法名:"+jp.getSignature().getName());
System.out.println(e);
System.out.println("异常通知");
}
}
2、配置切面
@Aspect
切面=切点+通知
package com.hs.service.impl;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
@Aspect
public class Log {
@Pointcut("execution(public void com.hs.service.impl.LogService*.*(..))")
public void pointCut(){}
@Before("pointCut()")
public void before(JoinPoint jp){
System.out.println(jp.getSignature().getName());
System.out.println("开始执行:"+new Date());
}
@After("pointCut()")
public void after(JoinPoint jp){
System.out.println(jp.getSignature().getName());
System.out.println("执行结束:"+new Date());
}
@AfterReturning("pointCut()")
public void afterReturning(JoinPoint jp){
System.out.println(jp.getSignature().getName());
System.out.println("后置通知执行结束:"+new Date());
}
@Around("pointCut()")
public void round(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕执行开始:"+new Date());
Object result=jp.proceed();//执行目标方法
System.out.println("执行结果是:"+result);
System.out.println("环绕执行结束:"+new Date());
}
@AfterThrowing(pointcut = "pointCut()",throwing = "e")
public void exception(JoinPoint jp,Throwable e){
System.out.println("方法名:"+jp.getSignature().getName());
System.out.println(e);
System.out.println("异常通知");
}
}
3、开启aop注解
<!-- 开启 aop注解扫描 -->
<aop:aspectj-autoproxy />
4、通知执行顺序
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4vkCKIZp-1632315227628)(spring框架.assets/N@FID8U[6KH%DHKZ52NSI@9.png)]
使用注解后,执行的顺序固定,方法的顺序不会影响执行的顺序
十五、spring jdbc
1、spring jdbc
对jdbc进行了封装,和我们的dbUtil类似,封装成了一个工具类JdbcTemplate。
2、导入依赖
spring-jdbc
spring-tx
<!-- spring jdbc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.14.RELEASE</version>
</dependency>
3、配置数据源
可以直接配置,也可以使用属性文件配置。
<!-- spring jdbc -->
<!-- 数据源-->
<!-- <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">-->
<!-- <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>-->
<!-- <property name="url" value="jdbc:mysql:///hscrm?serverTimezone=Asia/Shanghai"></property>-->
<!-- <property name="username" value="root"></property>-->
<!-- <property name="password" value="root"></property>-->
<!-- </bean>-->
<context:property-placeholder location="db.properties"></context:property-placeholder>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${hs.jdbc.driverName}"></property>
<property name="url" value="${hs.jdbc.url}"></property>
<property name="username" value="${hs.jdbc.username}"></property>
<property name="password" value="${hs.jdbc.password}"></property>
</bean>
4、配置jdbcTemplate
<!-- jdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
5、dao层使用jdbcTemplate完成CURD操作
(1)单行查询
// queryForObject查询结果最多一行
public Emp findEmpByUsername(String username){
String sql="select * from emp where username=?";
// Emp emp = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Emp>(Emp.class), username);
Object[] objs={username};
try {
Emp emp = jdbcTemplate.queryForObject(sql, objs, new BeanPropertyRowMapper<Emp>(Emp.class));
return emp;
}catch (Exception e){
return null;
}
}
(2)查询多行
// 查询所有
public List<Emp> findAllEmp(){
String sql="select * from emp";
List<Emp> empList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Emp>(Emp.class));
return empList;
}
(3)添加数据
// 添加
public int addEmp(Emp emp){
String sql="insert into emp values(null,?,?,?,?,?)";
int i=jdbcTemplate.update(sql,emp.getE_name(),emp.getE_sex(),emp.getE_tel(),emp.getUsername(),emp.getPasswd());
return i;
}
(4)修改数据
// 修改
public int updateEmp(Emp emp){
String sql="update emp set e_name=?,e_sex=?,e_tel=?,username=?,passwd=? where e_id=?";
int i=jdbcTemplate.update(sql,emp.getE_name(),emp.getE_sex(),emp.getE_tel(),emp.getUsername(),emp.getPasswd(),emp.getE_id());
return i;
}
(5)删除数据
// 删除
public int deleteEmp(int e_id){
String sql="delete from emp where e_id=?";
int i=jdbcTemplate.update(sql,e_id);
return i;
}
十六、事务管理
1、事务
在数据库中,用于完成一个功能的多个操作的集合。
比如:转账。
转账是一个事务,这个事务中包含两个操作,转出方扣钱,转入方加钱。
2、事务的特性ACID
(1)原子性
多个操作构成一个整体,不可分割。
(2)一致性
事务中的所有操作要么全部成功,要么全部失败。
(3)隔离性
可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
多个事务并发访问同一数据,可以设计隔离级别。
隔离级别越高,数据不一致问题影响越小,效率越低。
(4)持久性
一旦事务完成,数据永久存储。
3、spring事务管理
(1)spring框架对事务管理进行了封装
在数据库中:
获取事务;
执行事务中的所有操作;
如果发生异常,回滚所有操作rollback;
如果所有操作都没有异常,提交事务commit。
spring框架对其进行了封装:
spring事务管理主要用于service层。
获取一个事务;
执行service层的业务操作方法;
如果方法中没有异常,执行commit,数据库生效。
如果方法中有异常,执行rollback,数据库回滚。
4、spring事务管理器
Spring并不直接管理事务,而是提供了多种事务管理器。
Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。
Public interface PlatformTransactionManager()...{
// 由TransactionDefinition得到TransactionStatus对象
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
// 提交
Void commit(TransactionStatus status) throws TransactionException;
// 回滚
Void rollback(TransactionStatus status) throws TransactionException;
}
public interface TransactionDefinition {
int getPropagationBehavior(); // 返回事务的传播行为
int getIsolationLevel(); // 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
int getTimeout(); // 返回事务必须在多少秒内完成
boolean isReadOnly(); // 事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的
}
5、spring事务管理形式
(1)编程式事务管理
使用spring框架提供的api,自己写代码,实现事务管理。
try{
//事务操作
//事务提交
}catch(e){
//事务回滚
}
缺点:代码耦合度比较高。
优点:细粒度管理。
(2)声明式事务管理
底层是通过aop实现的。
不需要你写代码,配置一下就可以了。
配置形式:xml和注解。
优点:统一管理,低耦合。
缺点:粗细度管理。
十七、spring-声明式事务管理xml
1、问题案例:转账
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
<!--注解扫描-->
<context:component-scan base-package="com.hs"></context:component-scan>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///hscrm?serverTimezone=Asia/Shanghai"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
(1)数据库
CREATE TABLE `account` (
`aid` int(11) NOT NULL,
`aname` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`banlance` double(255,0) DEFAULT NULL,
`apasswd` varchar(255) COLLATE utf8_bin DEFAULT NULL,
PRIMARY KEY (`aid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
(2)dao层
package com.hs.domain;
public class Account {
private int aid;
private String aname;
private double banlance;
private String apasswd;
public int getAid() {
return aid;
}
public void setAid(int aid) {
this.aid = aid;
}
public String getAname() {
return aname;
}
public void setAname(String aname) {
this.aname = aname;
}
public double getBanlance() {
return banlance;
}
public void setBanlance(double banlance) {
this.banlance = banlance;
}
public String getApasswd() {
return apasswd;
}
public void setApasswd(String apasswd) {
this.apasswd = apasswd;
}
}
package com.hs.dao;
import com.hs.domain.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 转入:加钱
*/
public void addMoney(Account acc,double money){
String sql="update account set banlance=? where aid=?";
Object[] objs={acc.getBanlance()+money,acc.getAid()};
jdbcTemplate.update(sql,objs);
}
/**
* 转出:扣钱
*/
public void subMoney(Account acc,double money){
String sql="update account set banlance=? where aid=?";
Object[] objs={acc.getBanlance()-money,acc.getAid()};
jdbcTemplate.update(sql,objs);
}
}
(3)service层
package com.hs.service;
import com.hs.dao.AccountDao;
import com.hs.domain.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AccountService {
@Autowired
private AccountDao accountDao;
/**
* 转账
*/
public void transform(Account acc1, Account acc2,double money){
//扣钱
accountDao.subMoney(acc1,money);
//加钱
accountDao.addMoney(acc2,money);
}
}
(4)测试
package com.hs;
import com.hs.domain.Account;
import com.hs.service.AccountService;
import com.hs.util.SpringUtil;
import org.junit.Test;
public class Test1 {
@Test
public void test1(){
AccountService accountService = SpringUtil.getBean(AccountService.class);
Account acc1=new Account();
acc1.setAid(622001);
acc1.setBanlance(5000);
Account acc2=new Account();
acc2.setAid(622002);
acc2.setBanlance(5000);
accountService.transform(acc1,acc2,500);
}
}
2、修改service,测试
package com.hs.service;
import com.hs.dao.AccountDao;
import com.hs.domain.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AccountService {
@Autowired
private AccountDao accountDao;
/**
* 转账
*/
public void transform(Account acc1, Account acc2,double money){
//扣钱
accountDao.subMoney(acc1,money);
//异常
System.out.println(10/0);
//加钱
accountDao.addMoney(acc2,money);
}
}
出现异常:
java.lang.ArithmeticException: / by zero
at com.hs.service.AccountService.transform(AccountService.java:22)
at com.hs.Test1.test1(Test1.java:21)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
结果:扣钱成功,加钱失败
622001 zhangsan 4000 123456
622002 lisi 5500 123456
解决方案:事务管理。
3、spring声明式事务管理xml
基于aspectj配置事务管理:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
(1)配置事务管理器
<!-- 第一步,配置事务管理器-->
<!-- 指定数据源-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
(2)配置事务通知
<!-- 第二步,配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 事务属性:事务的传播性和隔离性,只读事务-->
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" isolation="DEFAULT"/>
</tx:attributes>
</tx:advice>
(3)配置事务管理
<!-- 第三步,配置aop切面-->
<aop:config>
<aop:pointcut id="cut" expression="execution(public void com.hs.service.AccountService.transform(..))"/>
<!--切面=切点+通知-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="cut"></aop:advisor>
</aop:config>
(4)测试
有异常时和没有异常时,看是否具有事务的一致性。
十八、事务管理注解形式
1、配置transactionManager
<!-- 第一步,配置事务管理器-->
<!-- 指定数据源-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
2、开启事务注解驱动
<!-- 第二步,开启事务注解驱动-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
3、在service 层的方法前加注解
第三步,加事务注解@Transactional
package com.hs.service;
import com.hs.dao.AccountDao;
import com.hs.domain.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class AccountService {
@Autowired
private AccountDao accountDao;
/**
* 转账
*/
@Transactional
public void transform(Account acc1, Account acc2,double money){
//扣钱
accountDao.subMoney(acc1,money);
//异常
// System.out.println(10/0);
//加钱
accountDao.addMoney(acc2,money);
}
}
十九、事务属性
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,readOnly = false)
1、事务的隔离级别
(1)并发事务引起的问题
在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务。并发虽然是必须的,但可能会导致一下的问题。
- 脏读(Dirty reads)——脏读发生在一个事务读取了另一个事务改写但尚未提交的数据时。如果改写在稍后被回滚了,那么第一个事务获取的数据就是无效的。
- 不可重复读(Nonrepeatable read)——不可重复读发生在一个事务执行相同的查询两次或两次以上,但是每次都得到不同的数据时。这通常是因为另一个并发事务在两次查询期间进行了更新。
- 幻读(Phantom read)——幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录。
(2)隔离级别
隔离级别 | 含义 |
---|---|
ISOLATION_DEFAULT | 使用后端数据库默认的隔离级别 |
ISOLATION_READ_UNCOMMITTED | 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读 |
ISOLATION_READ_COMMITTED | 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生 |
ISOLATION_REPEATABLE_READ | 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生 |
ISOLATION_SERIALIZABLE | 最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的 |
2、事务的传播性
spring定义了7中事务传播机制:
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED)
支持当前事务,如果没有事务会创建一个新的事务
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS)
支持当前事务,如果没有事务的话以非事务方式执行
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY)
支持当前事务,如果没有事务抛出异常
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW)
创建一个新的事务并挂起当前事务
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED)
以非事务方式执行,如果当前存在事务则将当前事务挂起
NEVER(TransactionDefinition.PROPAGATION_NEVER)
以非事务方式进行,如果存在事务则抛出异常
NESTED(TransactionDefinition.PROPAGATION_NESTED)
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
比如:
添加事务
方法a{
//操作1
//调用方法b
//操作2
}
添加事务:事务的传播机制
方法b{
操作3
}
3、只读事务
readOnly = false
readOnly = true
cut id=“cut” expression=“execution(public void com.hs.service.AccountService.transform(…))”/>
<aop:advisor advice-ref=“txAdvice” pointcut-ref=“cut”></aop:advisor>
</aop:config>
(4)测试
有异常时和没有异常时,看是否具有事务的一致性。
## 十八、事务管理注解形式
### 1、配置transactionManager
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
### 2、开启事务注解驱动
<tx:annotation-driven transaction-manager=“transactionManager”></tx:annotation-driven>
### 3、在service 层的方法前加注解
第三步,加事务注解@Transactional
package com.hs.service;
import com.hs.dao.AccountDao;
import com.hs.domain.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class AccountService {
@Autowired
private AccountDao accountDao;
/**
* 转账
*/
@Transactional
public void transform(Account acc1, Account acc2,double money){
//扣钱
accountDao.subMoney(acc1,money);
//异常
// System.out.println(10/0);
//加钱
accountDao.addMoney(acc2,money);
}
}
## 十九、事务属性
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,readOnly = false)
### 1、事务的隔离级别
(1)并发事务引起的问题
在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务。并发虽然是必须的,但可能会导致一下的问题。
> - 脏读(Dirty reads)——脏读发生在一个事务读取了另一个事务改写但尚未提交的数据时。如果改写在稍后被回滚了,那么第一个事务获取的数据就是无效的。
> - 不可重复读(Nonrepeatable read)——不可重复读发生在一个事务执行相同的查询两次或两次以上,但是每次都得到不同的数据时。这通常是因为另一个并发事务在两次查询期间进行了更新。
> - 幻读(Phantom read)——幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录。
(2)隔离级别
| 隔离级别 | 含义 |
| -------------------------- | ------------------------------------------------------------ |
| ISOLATION_DEFAULT | 使用后端数据库默认的隔离级别 |
| ISOLATION_READ_UNCOMMITTED | 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读 |
| ISOLATION_READ_COMMITTED | 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生 |
| ISOLATION_REPEATABLE_READ | 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生 |
| ISOLATION_SERIALIZABLE | 最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的 |
### 2、事务的传播性
spring定义了7中事务传播机制:
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED)
支持当前事务,如果没有事务会创建一个新的事务
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS)
支持当前事务,如果没有事务的话以非事务方式执行
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY)
支持当前事务,如果没有事务抛出异常
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW)
创建一个新的事务并挂起当前事务
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED)
以非事务方式执行,如果当前存在事务则将当前事务挂起
NEVER(TransactionDefinition.PROPAGATION_NEVER)
以非事务方式进行,如果存在事务则抛出异常
NESTED(TransactionDefinition.PROPAGATION_NESTED)
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
比如:
添加事务
方法a{
//操作1
//调用方法b
//操作2
}
添加事务:事务的传播机制
方法b{
操作3
}
### 3、只读事务
readOnly = false
readOnly = true