Spring快速回忆

本文深入探讨了Spring框架,包括其核心概念、架构组成、依赖注入、AOP、事务管理和MyBatis集成。Spring通过反转控制降低了组件间的耦合,提供了代理设计模式实现辅助功能的分离。AOP用于在关键点加入横切关注点,如事务管理。同时,文章介绍了如何通过注解简化Spring的配置和测试。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.概念

1.1 概念

  • Spring是一个项目管理框架,同时是一套JavaEE解决方案
  • Spring是众多优秀设计模式的组合(工厂、单例、代理、适配器、包装器、观察者、模板、策略)
  • Spring并未替代现有框架产品,而是将众多框架进行有机整合,简化企业级开发,俗称“胶水框架”

1.2 访问与下载

官网:https://spring.io/

下载地址:https://repo.spring.io/ui/native/release/org/springframework/spring/

2.Spring架构组成


Spring架构由诸多模块组成,可分类为

  • 核心技术:依赖注入,事件,资源,i18n,验证,数据绑定,类型转换,SpEL,AOP。
  • 测试:模拟对象,TestContext框架,Spring MVC测试, WebTestClient。
  • 数据访问:事务,DAO支持,JDBC,ORM,封送XML。
  • Spring MVC和Spring WebFlux Web框架。
  • 集成:远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。
  • 语言:Kotlin,Groovy,动态语言。

4.自定义工厂

创建.properties文件,编写类路径

userDao=dao.UserDaoImpl
userService=service.UserServiceImpl

自定义工厂类

import java.io.IOException;
import java.util.Properties;

public class MyFactoryUtil {
    private Properties properties = new Properties();
    private MyFactory(){}
    public MyFactory(String config) throws IOException {
        //加载配置文件
        properties.load(MyFactory.class.getResourceAsStream(config));
    }
    //获取对象(配置文件中的标志明)
    public Object getBean(String beanName) throws ClassNotFoundException,IllegalAccessException,
            InstantiationException{
        //获取类路径
        String classPath = properties.getProperty(beanName);
        if (classPath != null) {
            Class clz = null;
            //反射:加载类对象
            clz = Class.forName(classPath);
            //反射:获取对象
            return clz.newInstance();
        }
        return null;
    }
}

5.构建Spring项目

创建Maven项目,在pom中导入依赖

<!--spring依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.14</version>
</dependency>
<!--测试依赖-->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
</dependency>

创建Spring配置文件

一般名字为spring-context.xmlapplicationContext.xmlbeans.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--schema:规范,xxx.xsd-->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
                           http://www.springframework.org/schema/beans/spring-beans.xsd 
                           http://www.springframework.org/schema/context 
                           https://www.springframework.org/schema/context/spring-context.xsd">
    
    <!--要工厂生产的对象-->
    <bean id="userDao" class="dao.UserDaoImpl"></bean>
    <bean id="userService" class="service.UserServiceImpl"></bean>
</beans>

测试

@Test
public void testSpringFactory() {
    //启动工厂
    ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
    //获取对象
    UserDao userDao = (UserDao) context.getBean("userDap");
    userDao.deleteUser();
}

6.IOC

nverse of Controll:控制反转

反转了依赖关系的满足方式,由之前的自己创建依赖对象,变为由工厂推送。(变主动为被动,即反转)

解决了具有依赖关系的组件之间的强耦合,使得项目形态更加稳健

6.1 强耦合问题

强耦合:一个类中明确引用另一个类

<!--要工厂生产的对象-->
<bean id="userDao2" class="dao.UserDao2Impl"></bean>
<bean id="userService" class="service.UserServiceImpl">
    <!--userDao属性赋值-->
    <property name="userDao" ref="userDao2"></property>
</bean>

7.依赖注入

7.1 set注入

原理:通过回调set方法

有个User类和Address类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Integer id;
    private String password;
    private String sex;
    private Integer age;
    private Date bornDate;
    private String[] hobby;
    private Set<String> phones;
    private List<String> names;
    private Map<String, String> countries;
    private Properties properties;
    //自建类型(创建的类)
    private Address address;
}
<!--自建类型(Address)-->
<bean id="addr" class="entity.Address">
    <property name="id" value="1"/>
    <property name="city" value="beijing"/>
</bean>

<bean id="user" class="entity.User">
    <!--简单注入-->
    <property name="id" value="10"/>
    <property name="password" value="123"/>
    <property name="sex" value="male"/>
    <property name="age" value="20"/>
    <property name="bornDate" value="2020/12/12 12:20:30"/> <!--自动转换为日期时间类型-->
    <!--数组注入-->
    <property name="hobby" >
        <array>
            <value>football</value>
            <value>dance</value>
            <value>sing</value>
        </array>
    </property>
    <!--集合注入-->
    <property name="names" >
        <list>
            <value>tom</value>
            <value>jack</value>
        </list>
    </property>
    <property name="phones" >
        <set>
            <value>111</value>
            <value>222</value>
        </set>
    </property>
    <property name="countries" >
        <map>
            <entry key="zh" value="China"></entry>
            <entry key="en" value="English"></entry>
        </map>
    </property>
    <property name="properties" >
        <props>
            <prop key="url">jdbc:mysql:xxx</prop>
            <prop key="username">root</prop>
            <prop key="password">123456</prop>
        </props>
    </property>
    <!--自建类型注入-->
    <property name="address" ref="addr"></property>
</bean>

7.2 构造注入

原理:调用构造方法赋值

@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private Integer id;
    private String name;
    private String sex;
    private Integer age;
}
<bean id="student" class="entity.Student">
    <constructor-arg name="id" value="2"/>
    <constructor-arg name="name" value="shine"/>
    <constructor-arg name="sex" value="male"/>
    <constructor-arg name="age" value="20"/>
</bean>

7.3 自动注入

同名注入:byName

原理:自动查找当前工厂中存在的与需要注入的对象属性同名的bean,存在则将bean注入到对象中

public class UserServiceImpl implements UserService{
    private UserDao userDao;
}
<bean id="userDao" class="dao.UserDaoImpl"></bean>
<!--自动注入-->
<bean id="userService" class="service.UserServiceImpl" autowire="byName">

同类型注入:byType

原理:查找当前对象属性在工厂中是否有相同属性的bean存在

当存在多个相同属性的bean时将会报错

public class UserServiceImpl implements UserService{
    private UserDao userDao;
}
<bean id="userDao" class="dao.UserDaoImpl"></bean>
<!--自动注入-->
<bean id="userService" class="service.UserServiceImpl" autowire="byType">

8.bean细节

8.1 单例多例

<!--单例(默认)|多例-->
<bean scope="singleton|prototype">

8.2 复杂对象

指创建过程复杂

例子:创建mysql连接对象

//FactoryBean类
public class MyConnectionFactoryBean implements FactoryBean<Connection> {
    @Override
    public Connection getObject() throws Exception {
        Class.forName("com.mysql.jdbc.Driver");
        return DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis_shine", "root", "123456");
    }

    @Override
    public Class<?> getObjectType() {
        return Connection.class;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }
}
<!--mysql依赖-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.49</version>
</dependency>
<!--复杂对象:FactoryBean-->
<!--复杂对象:当从工厂索要一个bean时,如果是FactoryBean,实际返回的是工厂bean的getObject-->
<bean id="conn" class="factorybean.MyConnectionFactoryBean"></bean>
//测试
@Test
public void testFactory() throws SQLException {
    //启动工厂
    ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
    Connection conn = (Connection) context.getBean("conn");
    //返回工厂本身
    //MyConnectionFactoryBean factoryBean = (MyConnectionFactoryBean) context.getBean("&conn");
    PreparedStatement preparedStatement = conn.prepareStatement("select * from t_user ");
    ResultSet resultSet = preparedStatement.executeQuery();
    resultSet.next();
    System.out.println(resultSet.getInt("id"));
}

9.Spring工厂特性

9.1 饿汉式创建优势

  • 工厂创建之后,会将Spring配置文件中的所有对象都创建完成(饿汉式)。
  • 提高程序运行效率。避免多次I0,减少对象创建时间。(概念接近连接池,一次性创建好,使用时直接获取)

9.2 生命周期方法

  • 自定义初始化方法:添加“init-method"属性,Spring则会在创建对象之后,调用此方法。
  • 自定义销毁方法:添加“destroy-method"属性,Spring则会在销毁对象之前,调用此方法。
  • 销毁:工厂的close()方法被调用之后,Spring会毁掉所有已创建的单例对象。
  • 分类:Singleton对象由Spring容器销毁、Prototype对象由JVM销毁。

生命周期注解,写在方法前一行

//初始化方法注解
@PostConstruct
//销毁方法注解
@PreDestroy
<!--或者指定初始化方法和销毁方法-->
<bean id="addr" class="entity.Address" init-method="init_chh" destroy-method="destroy_xh">
    <property name="id" value="1"/>
    <property name="city" value="beijing"/>
</bean>

9.4 生命周期阶段

单例bean: singleton

随工厂启动创建=》一构造方法―=》set方法(注入值)=》init(初始化) =》构建完成=-》随工厂关闭销毁

多例bean: prototype

被使用时创建==-》构造方法―=-》set方法(注入值) =-》init(初始化) =-》构建完成=-》JVM垃圾回收销毁

10.代理设计模式

10.1 概念

将核心功能与辅助功能(事务、日志、性能监控代码)分离,达到核心业务功能更纯粹、辅助业务功能可复用。

image-20220217145807670

10.2 静态代理设计模式

通过代理类的对象,为原始类的对象(目标类的对象)添加辅助功能,更容易更换代理实现类、利于维护。

房东出租房屋:房东类和中介类实现相同业务接口(包括核心业务和辅助功能),房东类负责核心业务,中介类负 责辅助功能,在中介类中调用房东类实现整体业务流程

10.3动态代理

动态创建代理类的对象,为原始类的对象添加辅助功能。


jdk动态代理

基于接口

利用租房例子

@Test
public void testJDK(){
    //目标
    FangdongService fangdongService = new FangdongServiceImpl();

    //额外功能
    InvocationHandler ih = new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //辅助功能、额外功能
            System.out.println("辅助功能1");
            System.out.println("辅助功能2");
            //核心
            fangdongService.zufang();
            return null;
        }
    };

    //动态生成代理类
    FangDongService proxy = (FangDongService) Proxy.newProxyInstance(DynamicProxyTest.class.getClassLoader(),
                                                                     fangdongService.getClass().getInterfaces(), ih);
    proxy.zufang();
}

CGlib动态代理

基于继承

利用租房例子

@Test
public void testCGlib(){
    //目标
    FangdongService fangdongService = new FangdongServiceImpl();

    //CGlib调用
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(FangdongServiceImpl.class);
    enhancer.setCallback(new org.springframework.cglib.proxy.InvocationHandler() {
        @Override
        public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
            //辅助功能、额外功能
            System.out.println("辅助功能1");
            System.out.println("辅助功能2");
            //核心
            fangdongService.zufang();
            return null;
        }
    });

    //动态生成代理类
    FangdongServiceImpl proxy = (FangdongServiceImpl)enhancer.create();
    proxy.zufang();
}

11.AOP

11.1概念

AOP (Aspect Oriented Programming),即面向切面编程,利用一种称为"横切"的技术,剖开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"“切面”,简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

14.2 AOP开发术语

  • 连接点(Joinpoint):连接点是程序类中客观存在的方法,可被Spring拦截并切入内容。
  • 切入点(Pointcut):被Spring切入连接点。
  • 通知、增强(Advice):可以为切入点添加额外功能,分为:前置通知、后置通知、异常通知、环绕通知等。
  • 目标对象(Target):代理的目标对象
  • 引介(Introduction):一种特殊的增强,可在运行期为类动态添加Field和Method。
  • 织入(Weaving):把通知应用到具体的类,进而创建新的代理类的过程。
  • 代理(Proxy):被AOP织入通知后,产生的结果类。
  • 切面(Aspect):由切点和通知组成,将横切逻辑织入切面所指定的连接点中。

11.3 作用

Spring的AOP编程即是通过动态代理类为原始类的方法添加辅助功能。

11.4 环境搭建

引入aop依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.6</version>
</dependency>

spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>

11.5 springAOP开发流程

  • 通过AOP提供的编码流程,更便利的定制切面,更方便的定制了动态代理。
  • 进而彻底解决了辅助功能冗余的问题;
  • 业务类中职责单—性得到更好保障;
  • 辅助功能也有很好的复用性。

创建相关类和方法

//UserService接口
public interface UserService {
    public List<User> queryUsers();
    public Integer updateUser();
    public Integer saveUser();
    public Integer deleteUser();
}
//UserService实现类
public class UserServiceImpl implements UserService{
    @Override
    public List<User> queryUsers() {
        System.out.println("queryUsers()");
        return new ArrayList<>();
    }
    @Override
    public Integer updateUser() {
        System.out.println("updateUser()");
        return 1;
    }
    @Override
    public Integer saveUser() {
        System.out.println("saveUser()");
        return 1;
    }
    @Override
    public Integer deleteUser() {
        System.out.println("deleteUser()");
        return 1;
    }
}

创建前置通知类

  • 辅助功能在前:前置通知
  • 辅助功能在后:后置通知
  • 前后都要辅助功能:环绕通知
//前置通知类
public class MyBeforeAdvice implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        //额外功能
        System.out.println("事务控制");
        System.out.println("日志打印");
    }
}

编辑配置文件,定义切入点形成切面

<!--目标:原始业务类-->
<bean id="userService" class="service.UserServiceImpl"/>
<!--通知:额外功能-->
<bean id="before" class="advice.MyBeforeAdvice"/>

<!--定义切入点-->
<aop:config>
    <!--切入点 修饰符 返回值 包.类 方法名 参数表-->
    <aop:pointcut id="pc_shine" expression="execution(* queryUsers())"/>
    <!--组装-->
    <aop:advisor advice-ref="before" pointcut-ref="pc_shine"/>
</aop:config>

测试

@Test
public void testSpringAop(){
    ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");

    //通过目标id获得代理对象
    UserService proxy = (UserService) context.getBean("userService");
    System.out.println(proxy.getClass());
    proxy.queryUsers();
}

11.6 通知类

  • 前置通知: MethodBeforeAdvice
  • 后置通知:AfterAdvice
  • 后置通知: AfterReturningAdvice 有异常不执行,方法会因异常而结束,无返回值
  • 异常通知:ThrowsAdvice
  • 环绕通知: MethodInterceptor

11.7 切入点

<!--匹配参数-->
<aop:pointcut id="myPointCut" expression="execution(* *(aaron.aop.basic.User))"/>
<!--匹配方法名(无参)-->
<aop:pointcut id="myPointCut" expression="execution(* save())"/>
<!--匹配方法名(任意参数)-->
<aop:pointcut id=" myPointCut" expression=" execution(* save( ..))"/>
<!--匹配返回值类型-->
<aop:pointcut id="myPointCut" expression=" execution( aron.aop.basic.User *(..))"/>
<!--匹配类名-->
<aop:pointcut id="myPointCut " expression="execution(* aaron.aop.basic.UserServiceImp1.*(..))"/>
<!--匹配包名-->
<aop:pointcut id="myPointCut" expression=" execution(* aaron.aop.basic.*.*(..))"/>
<!--匹配包名、以及子包名-->
<aop:pointcut id="myPointCut" expression="execution(* aaron.aop..*.*( . .))"/>

11.8 JDK和CGlib选择

  • spring底层,包含了jdk代理和cglib代理两种动态代理生成机制
  • 基本规则是:目标业务类如果有接口则用JDK代理,没有接口则用CGLib代理

11.9 后处理器

  • spring中定义了很多后处理器;
  • 每个bean在创建完成之前,都会有一个后处理过程,即再加工,对bean做出相关改变和调整。
  • spring-AOP中,就有一个专门的后处理器,负责通过原始业务组件(Service),再加工得到一个代理组件。
public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("后处理1");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("后处理2");
        return bean;
    }
}
<bean class="processor.MyBeanPostProcessor"/>

12.MyBatis连接池

12.1 集成

依赖

<!--lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
</dependency>
<!--spring-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.14</version>
</dependency>
<!--aop-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.6</version>
</dependency>
<!--jdbc-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.12</version>
</dependency>
<!--spring整合mybatis-->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.3.2</version>
</dependency>
<!--mybatis-->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
</dependency>
<!--mysql驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.49</version>
</dependency>
<!--Druid-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.6</version>
</dependency>
<!--单元测试-->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
</dependency>

创建jdbc.properties

jdbc.url = jdbc:mysql://localhost:3306/mybatis_shine?serverTimezone=UTC&useUnicode=true&useSSL=false&characterEncoding=UTF-8
jdbc.driver = com.mysql.cj.jdbc.Driver
jdbc.username = root
jdbc.password = 123456
jdbc.init = 1
jdbc.minIdle = 1
jdbc.maxActive = 3

编写spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd
                           http://www.springframework.org/schema/context
                           https://www.springframework.org/schema/context/spring-context.xsd">

    <context:property-placeholder location="classpath:jdbc-properties"/>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>

        <!--配置初始化大小、最小、最大-->
        <property name="initialSize" value="${jdbc.init}" />
        <property name= "minIdle" value="${jdbc.minIdle}" />
        <property name= "maxActive" value="${jdbc.maxActive}" />

        <!--配置获取连接等待超时的时间-->
        <property name= "maxWait" value="60000" />
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒-->
        <property name="timeBetweenEvictionRunsMillis" value="60000" />
        <!--配置一个连接在池中最小生存的时间,单位是毫秒-->
        <property name= "minEvictableIdleTimeMillis" value="300000" />
    </bean>
</beans>
12.1.1 配置sqlSessionFactory
<!--生产SqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!--注入连接池-->
    <property name="dataSource" ref="dataSource"></property>
    <!-- 注入dao-mapper文件信息,如果映射文件和dao接口同包且同名,则此配置可省略-->
    <property name="mapperLocations">
        <list>
            <value>classpath:../resources/mapper/*.xml</value>
        </list>
    </property>
    <!--为dao-mapper文件中的实体定义缺省包路径
        如: <select id="queryAll" resultType="User">中 User类可以不定义包-->
    <property name="typeAliasesPackage" value="entity"></property>
</bean>
12.1.2配置mapperScannerConfigurer

管理DAO实现类的创建,并创建DAO对象,存入工厂管理

  • 扫描所有DAO接口,去构建DAO实现
  • 将DAO实现存入工厂管理
  • DAO实现对象在工厂中的id是:“首字母小写的-接口的类名”,例如:UserDAO==>userDao
<!-- mapperScannerConfigurer -->
<bean id="mapperScannerConfigurer9" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <!-- dao接口所在的包如果有多个包,可以用逗号或分号分隔
         <property name="basePackage" value="com.a.dao,com.b.dao"></property>
         -->
    <property name="basePackage" value="dao"></property>
    <!--如果工厂中只有一个SqlSessionFactory的bean,此配置可省略-->
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
12.1.3配置service
<bean id="userService" class="service.UserServiceImpl">
    <!--注意ref中的值是对应Dao接口的首字母小写的接口名-->
    <property name="userDao" ref="userDao"></property>
</bean>

13.事务

13.1 配置DataSourceTransactionManager

事务管理器,其中持有DataSource,可以控制事务功能(commit,rollback等)。

DataSourceTransactionManager和SqlSessionFactoryBean要注入同一个DataSource的Bean,否则事务控制失败

<!-- 1.引入一个事务管理器,其中依赖DataSource,借以获得连接,进而控制事务逻辑-->
<bean id="tx" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

13.2 配置事务通知

基于事务管理器,进一步定制,生成一个额外功能:Advice。

此Advice可以切入任何需要事务的方法,通过事务管理器为方法控制事务。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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/tx
                           http://www.springframework.org/schema/tx/spring-tx.xsd">

    <tx:advice id="txManager" transaction-manager="tx">
        <!--事务属性-->
        <tx:attributes>
            <!--<tx:method name="insertUser" rollback-for="Exception" isolation="DEFAULT"
        propagation="REQUIRED" read-only="false" />-->
            <!--以User结尾的方法,切入此方法时,采用对应事务实行-->
            <tx:method name="*User" rollback-for="Exception" />
            <!-- 以query开头的方法,切入此方法时,采用对应事务实行-->
            <tx:method name= "query*" propagation="SUPPORTS" />
            <!--剩余所有方法-->
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
</beans>

13.3事务属性

13.3.1 隔离级别

isolation:隔离级别

default          (默认值)(采用数据库的默认的设置)(建议)
read-uncommited  读未提交
read-commited    读提交(Oracle数据库默认的隔离级别)
repeatable-read  可重复读(MySQL数据库默认的隔离级别)
serialized-read  序列化读

隔离级别由低到高为: read-uncommited <read-commited <repeatable-read < serialized-read

安全性:级别越高,多事务并发时,越安全。因为共享的数据越来越少,事务间彼此干扰减少。
并发性:级别越高,多事务并发时,并发越差。因为共享的数据越来越少,事务间阻塞情况增多。

事务并发时的安全问题:

脏读:一个事务读取到另一个事务还未提交的数据。
大于等于read-commited可防止

不可重复读:一个事务内多次读取一行数据的相同内容,其结果不一致。
大于等于repeatable-read 可防止

幻影读:一个事务内多次读取一张表中的相同内容,其结果不一致。
serialized-read 可防止
13.3.2 传播行为

propagation:传播行为

当涉及到事务嵌套(Service调用Service)中,可能会存在问题。
  SUPPORTS =不存在外部事务,则不开启新事务;存在外部事务,则合并到外部事务中。(适合查询)
  REQUIRED = 不存在外部事务,则开启新事务;存在外部事务,则合并到外部事务中。(默认值)(适合增删改)
13.3.3可读性

readonly:读写性

true:只读,可提高查询效率。(适合查询)
false:可读可写。(默认值)(适合增删改)
13.3.4 事务超时

timeout:事务超时时间

当前事务所需操作的数据被其他事务占用,则等待。
  100:自定义等待时间10日(秒)。
  -1:由数据库指定等待时间,默认值。(建议)
13.3.5 事务回滚

rollback-for 回滚属性

如果事务中抛出RuntimeException,则自动回滚
如果事务中抛出 CheckException(非运行时异常Exception),不会自动回滚,而是默认提交事务
处理方案︰将CheckException转换成RuntimException上抛,或设置rollback-for= "exception"

13.4 编织

将事务管理的Advice切入需要事务的业务方法中

<aop:config>
    <aop:pointcut expression="execution(* service.UserServiceImpl.*( ..) )" id="pc" />
    <!--组织切面-->
    <aop:advisor advice-ref="txManager" pointcut-ref="pc" />
</aop:config>

14.注解

14.1 声明bean

用于替换自建类型组件的<bean…标签;可以更快速的声明bean

  • Service业务类专用
  • @Repository dao实现类专用@Controller web层专用
  • @Component通用
  • Scope用户控制bean的创建模式
// @Service说明此类是一个业务类,需要将此类纳入工厂等价替换掉<bean class=" xxx.UserServiceImpl""l
//@Service默认beanId ==首字母小写的类名"userserviceImpl"
//@Service( "userService" )自定义beanId为"userService"
@Service //声明bean,且id="userServiceImpl"
@Scope("singleton")//声明创建模式,默认为单例模式;@Scope("prototype")即可设置为多例模式
public class UserServiceImpl implements UserService {}

14.2 注入

用于完成Bean中属性的注入


  • @Autowired基于类型自动注入
  • @Resource基于名称自动注入
  • @Qualifier( “userDAO”)限定要自动注入的bean的id,一般和@Autowired联用
  • @value注入简单类型数据(jdk8种+String)
@Service
public class UserServiceImpl implements UserService {
    @Autowired //注入类型为UserDao的bean
    @Qualifier("userDA02")//如果有多个类型为UserDao的bean,可以用此注解从中挑选一个
    private UserDao userDao;
}
@Service
public class UserServiceImpl implements UserService {
	@Resource( "userDao3" )//注入id="userDao3"的bean
    private UserDao userDao;
    /*
    @Resource //注入id="userDAO"的beanprivate UserDao userDao ;
    */
}
public class XX{
	@Value( "108" ) //注入数字
    private Integer id;
	@Value( "shine" ) //注入String
    private String name;
}

14.3 事务控制

用于控制事务切入


Transactional

工厂配置中的<tx:advice…和<aop:config…可以省略!!

//类中的每个方法都切入事务(有自己的事务控制的方法除外)
@Transactional(isolation=Isolation.READ_CONMITTED,propagation=Propagation.REQUIRED,readOnly=false, rollbakFor=Exception.class,timeout = -1)
public class UserServiceImpl implements UserService {
    //该方法自己的事务控制,仅对此方法有效
    @Transactional(propagation=Propagation.SUPPORTS)
    public List<User> queryAll() {
    	return userDao.queryAll();
    public void save(User user){
        userDao.save(user) ;
    }
}

14.4 注解所需配置

<!--告知spring,哪些包中有被注解的类、方法、属性-->
<context:component-scan base-package="net.lj"></context:component-scan>
<!--告知spring,@Transactional在定制事务时,基于txManager=DataSourceTransactionManager --><tx:annotation-driven transaction-manager="txManager"/>

14.5 AOP注解

使用

@Aspect //声明此类是一个切面类:会包含切入点(pointcut)和通知(advice)
@Component //声明组件,进入工厂
public class MyAspect {
    //定义切入点
    @Pointcut(" execution(* service.UserServiceImpl.*( ..))")
    public void pc() {
    }

    @Before("pc()")//前置通知
    public void mybefore(JoinPoint a) {
        System.out.println("target:" + a.getTarget());
        System.out.println("args:" + a.getArgs());
        System.out.println("method' s name:" + a.getSignature().getName());
        System.out.println("before~~~~");
    }

    @AfterReturning(value="pc() " , returning="ret") //后置通知
    public void myAfterReturning( JoinPoint a , Object ret) {
        System.out.println("after~~~~ : " + ret);
    }

    @Around( "pc()")//环绕通知
    public Object myInterceptor(ProceedingJoinPoint p) throws Throwable {
        System.out.println("interceptor1~rw~");
        Object ret = p.proceed();
        System.out.println("interceptor2~~~~");
        return ret;
    }

    @AfterThrowing(value="pc()" ,throwing="ex" ) //异常通知
    public void myThrows ( JoinPoint jp,Exception ex){
        System.out. println( "throws" );
        System . out. println( "===="+ex. getMessage() );
    }
}

配置

<!--添加如下配置,启用aop注解-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

15.集成Junit

15.1 导入依赖

<!--单元测试-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.14</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
</dependency>

15.2 编码

  • 可以免去工厂的创建过程;
  • 可以直接将要测试的组件注入到测试类。
//测试启动,启动spring工厂,并且当前测试类会被工厂生产
@RunWith(SpringJUnit4ClassRunner.class) //由SpringJUnit4ClassRunner启动测试
@ContextConfiguration("classpath:applicationContext.xml") //spring的配置文件位置
public class SpringTest { //当前测试类也会被纳入工厂中,所以其中属性可以注入

    @Autowired //注入要测试的组件
    @Qualifier("userDao")
    private UserDao userDao;

    @Test
    public void test() {
        //测试使用userDao
        userDao.queryUser();
        ....
    }
}
### 解决PyCharm无法加载Conda虚拟环境的方法 #### 配置设置 为了使 PyCharm 能够成功识别并使用 Conda 创建的虚拟环境,需确保 Anaconda 的路径已正确添加至系统的环境变量中[^1]。这一步骤至关重要,因为只有当 Python 解释器及其关联工具被加入 PATH 后,IDE 才能顺利找到它们。 对于 Windows 用户而言,在安装 Anaconda 时,默认情况下会询问是否将它添加到系统路径里;如果当时选择了否,则现在应该手动完成此操作。具体做法是在“高级系统设置”的“环境变量”选项内编辑 `Path` 变量,追加 Anaconda 安装目录下的 Scripts 文件夹位置。 另外,建议每次新建项目前都通过命令行先激活目标 conda env: ```bash conda activate myenvname ``` 接着再启动 IDE 进入工作区,这样有助于减少兼容性方面的问题发生概率。 #### 常见错误及修复方法 ##### 错误一:未发现任何解释器 症状表现为打开 PyCharm 新建工程向导页面找不到由 Conda 构建出来的 interpreter 列表项。此时应前往 Preferences/Settings -> Project:...->Python Interpreter 下方点击齿轮图标选择 Add...按钮来指定自定义的位置。按照提示浏览定位到对应版本 python.exe 的绝对地址即可解决问题。 ##### 错误二:权限不足导致 DLL 加载失败 有时即使指定了正确的解释器路径,仍可能遇到由于缺乏适当的操作系统级许可而引发的功能缺失现象。特别是涉及到调用某些特定类型的动态链接库 (Dynamic Link Library, .dll) 时尤为明显。因此拥有管理员身份执行相关动作显得尤为重要——无论是从终端还是图形界面触发创建新 venv 流程均如此处理能够有效规避此类隐患。 ##### 错误三:网络连接异常引起依赖下载超时 部分开发者反馈过因网速慢或者其他因素造成 pip install 操作中途断开进而影响整个项目的初始化进度条卡住的情况。对此可尝试调整镜像源加速获取速度或是离线模式预先准备好所需资源包后再继续后续步骤。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值