1.1.工厂模式解耦
1.1.1原始方式开发
public interface AccountDao { /** * 保存账户 */ void save(); }
public class AccountDaoImpl implements AccountDao { /** * 保存账户 */ @Override public void save() { System.out.println("AccountDaoImpl... save()"); } }
public interface AccountService { /** * 保存账户 */ void save(); }
public class AccountServiceImpl implements AccountService { /** * 保存账户 */ @Override public void save() { System.out.println("AccountServiceImpl... save()"); AccountDao accountDao = new AccountDaoImpl(); accountDao.save(); } }
public class Client { public static void main(String[] args) { AccountService accountService = new AccountServiceImpl(); accountService.save(); } }
1.2.2使用工厂模式解耦
1.2.2.1工厂模式解耦思路
在实际开发中我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候, 让一个类中的方法通过读取配置文件,把这些对象创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。那么,这个读取配置文件, 创建和获取三层对象的类就是工厂
1.2.2.2实现
<dependencies> <!-- 解析 xml 的 dom4j --> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> <!-- dom4j 的依赖包 jaxen --> <dependency> <groupId>jaxen</groupId> <artifactId>jaxen</artifactId> <version>1.1.6</version> </dependency> <!--单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies>
public interface AccountDao { /** * 保存账户 */ void save(); }
public class AccountDaoImpl implements AccountDao { /** * 保存账户 */ @Override public void save() { System.out.println("AccountDaoImpl... save()"); } }
public interface AccountService { /** * 保存账户 */ void save(); }
public class AccountServiceImpl implements AccountService { //使用工厂对象获取指定对象的实例 private AccountDao accountDao = (AccountDao) BeanFactory.getBean("accountDao"); /** * 保存账户 */ @Override public void save() { System.out.println("AccountServiceImpl... save()"); accountDao.save(); } }
public class Client { //使用工厂对象获取指定对象的实例 private static AccountService accountService = (AccountService) BeanFactory.getBean("accountService"); public static void main(String[] args) { accountService.save(); } }
BeanFactory.java
下面的通过 BeanFactory 中 getBean 方法获取对象就解决了我们代码中对具体实现类的依赖。
public class BeanFactory { //定义map private static Map<String, Object> beans; static{ try { beans = new HashMap<String, Object>(); //解析xml, 初始化beans集合 //1. 创建SaxReader对象 SAXReader saxReader = new SAXReader(); //2. 读取配置文件 获得document对象 Document document = saxReader.read(BeanFactory.class.getClassLoader().getResourceAsStream("applicationContext.xml")); //3. 获得所有的bean标签对象List List<Element> beanList = document.selectNodes("//bean"); //4. 遍历 for (Element element : beanList) { //获得id的属性值作为map的key String id = element.attributeValue("id"); //获得class的属性值,反射得到对象作为map的value String className = element.attributeValue("class"); beans.put(id, Class.forName(className).newInstance()); } } catch (Exception e) { e.printStackTrace(); } } /*** * 根据配置文件中的节点ID获取指定的对象实例 * @param id * @return */ public static Object getBean(String id){ return beans.get(id); } }
<?xml version="1.0" encoding="UTF-8"?> <beans> <!--id就是接口的名字, class实现类的全限定名 --> <bean id="accountDao" class="com.ac.dao.impl.AccountDaoImpl"></bean> <bean id="accountService" class="com.ac.service.impl.AccountServiceImpl"></bean> </beans>
1.创建Maven工程
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> <!--单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies>
2.接口和实现类
public interface AccountDao { /** * 保存账户 */ void save(); }
public class AccountDaoImpl implements AccountDao { /** * 保存账户 */ @Override public void save() { System.out.println("AccountDaoImpl---saveAccount()"); } }
public interface AccountService { /** * 保存账户 */ void save(); }
public class AccountServiceImpl implements AccountService { /** * 保存账户 */ @Override public void save() { System.out.println("AccountServiceImpl---saveAccount()"); AccountDao accountDao = new AccountDaoImpl();//TODO 耦合待解决 accountDao.save(); } }
public class Client { public static void main(String[] args) { AccountService accountService = new AccountServiceImpl();//TODO 耦合待解决 accountService.save(); } }
3.在类的根路径下创建spring的配置文件
配置文件为任意名称的 xml 文件(不能是中文),一般习惯名称为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"> <!--注册Service--> <bean id="accountService" class="com.ac.service.impl.AccountServiceImpl"></bean> <!--注册Dao--> <bean id="accountDao" class="com.ac.dao.impl.AccountDaoImpl"></bean> </beans>
4.测试
public class SpringTest { /*** * 使用SpringIOC容器创建对象实例 * @throws Exception */ @Test public void testSpringContext() throws Exception { /** BeanFactory 才是 Spring 容器中的顶层接口。 ApplicationContext 是它的子接口。 BeanFactory 和 ApplicationContext 的区别: 创建对象的时间点不一样。 ApplicationContext:只要一读取配置文件,默认情况下就会创建对象。 BeanFactory:什么使用什么时候创建对象。 ClassPathXmlApplicationContext: 它是从类的根路径下加载配置文件 推荐使用这种 FileSystemXmlApplicationContext: 它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。 AnnotationConfigApplicationContext: 当我们使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。 **/ //1. 创建工厂 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); //2. 根据id获得对象 AccountService accountService = (AccountService) applicationContext.getBean("accountService"); accountService.save(); } }
<?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"> <!--注册Service--> <bean id="accountService" class="com.ac.service.impl.AccountServiceImpl" scope="singleton" init-method="initMethod" destroy-method="destoryMethod"></bean> <!--注册Dao--> <bean id="accountDao" class="com.ac.dao.impl.AccountDaoImpl"></bean> </beans>
作用: 用于配置对象让 spring 来创建的。 默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。 属性: id:给对象在容器中提供一个唯一标识。用于获取对象。 class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。 scope:指定对象的作用范围。 * singleton :默认值,单例的. * prototype :多例的. * request :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中. * session :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中. * global session :WEB 项目中,应用在 Portlet 环境.如果没有 Portlet 环境那么 globalSession 相当于 session. init-method:指定类中的初始化方法名称。 destroy-method:指定类中销毁方法名称。并且需要是单例模式才可以调用(因为多利模式下bean的生命周期归JVM管,单例模式下Bean的生命周期归spring 管)
单例对象:scope="singleton" :一个应用只有一个对象的实例。它的作用范围就是整个引用。 生命周期: 对象出生:当应用加载,创建容器时,对象就被创建了。 对象活着:只要容器在,对象一直活着。 对象死亡:当应用卸载,销毁容器时,对象就被销毁了。 多例对象:scope="prototype" :每次访问对象时,都会重新创建对象实例。 生命周期: 对象出生:当使用对象时,创建新的对象实例。 对象活着:只要对象在使用中,就一直活着。 对象死亡:当对象长时间不用时,被 java 的垃圾回收器回收了。
1.第一种方式:使用默认无参构造函数(必须有构造函数)
第一种方式:使用默认无参构造函数 <!--在默认情况下: 它会根据默认无参构造函数来创建类对象。如果 bean 中没有默认无参构造函数,将会创建失败。 --> <bean id="accountService" class="com.ac.service.impl.AccountServiceImpl"/>
2.第二种方式:spring管理静态工厂-使用静态工厂的方法创建对象(使用场景:例如类中的静态方法)
/** * 模拟一个静态工厂,创建业务层实现类 */ public class StaticFactory { public static IAccountService createAccountService(){ return new AccountServiceImpl(); } } <!-- 此种方式是: 使用 StaticFactory 类中的静态方法 createAccountService 创建对象,并存入 spring 容器 id 属性:指定 bean 的 id,用于从容器中获取 class 属性:指定静态工厂的全限定类名 factory-method 属性:指定生产对象的静态方法 --> <bean id="accountService" class="com.ac.factory.StaticFactory" factory-method="createAccountService"></bean>
3.第三种方式:spring 管理实例工厂-使用实例工厂的方法创建对象**(使用场景:引入外部包的时候)**
/** * 模拟一个实例工厂,创建业务层实现类 * 此工厂创建对象,必须现有工厂实例对象,再调用方法 */ public class InstanceFactory { public IAccountService createAccountService(){ return new AccountServiceImpl(); } } <!-- 此种方式是:先把工厂的创建交给 spring 来管理。然后在使用工厂的 bean 来调用里面的方法 factory-bean 属性:用于指定实例工厂 bean 的 id。 factory-method 属性:用于指定实例工厂中创建对象的方法 --> <bean id="instancFactory" class="com.ac.factory.InstanceFactory"></bean> <bean id="accountService" factory-bean="instancFactory" factory-method="createAccountService"></bean>
依赖注入全称是 dependency Injection(di) 翻译过来是依赖注入.其实就是如果我们托管的某一个类中存在属性,需要spring在创建该类实例的时候,顺便给这个对象里面的属性进行赋值。 这就是依赖注入。
现在, Bean的创建交给Spring了, 需要在xml里面进行注册
我们交给Spring创建的Bean里面可能有一些属性(字段), Spring帮我创建的同时也把Bean的一些属性(字段)给赋值, 这个赋值就是注入.
DI:简单来说其实就是对对象属性的赋值过程。(di来赋值)
public class AccountServiceImpl implements AccountService { private String name; private Integer age; private Date birthday; /** * 带参数构造函数,可以给成员变量name赋值 * @param name */ public AccountServiceImpl(String name, Integer age, Date birthday) { this.name = name; this.age = age; this.birthday = birthday; } /** * 保存账户 */ @Override public void save() { System.out.println("AccountServiceImpl---saveAccount()"+name); } }
配置文件
<!--注册AccountService--> <!-- 使用构造函数的方式,给 service 中的属性传值 要求: 类中需要提供一个对应参数列表的构造函数。 涉及的标签: constructor-arg 属性: index:指定参数在构造函数参数列表的索引位置 type:指定参数在构造函数中的数据类型 name:指定参数在构造函数中的名称 用这个找给谁赋值 =======上面三个都是找给谁赋值,下面两个指的是赋什么值的============== value:它能赋的值是基本数据类型和 String 类型 ref:它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean --> <bean id="accountService" class="com.ac.service.impl.AccountServiceImpl"> <constructor-arg name="name" value="张三"></constructor-arg> <constructor-arg name="age" value="18"></constructor-arg> <constructor-arg name="birthday" ref="now"></constructor-arg> </bean> <bean id="now" class="java.util.Date"></bean>
public class AccountServiceImpl implements IAccountService { private String name; private Integer age; private Date birthday; public void setName(String name) { this.name = name; } public void setAge(Integer age) { this.age = age; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override public void saveAccount() { System.out.println(name+","+age+","+birthday); } }
配置文件
<!-- 通过配置文件给 bean 中的属性传值:使用 set 方法的方式 涉及的标签: property 属性: name:找的是类中 set 方法后面的部分 ref:给属性赋值是其他 bean 类型的 value:给属性赋值是基本数据类型和 string 类型的 实际开发中,此种方式用的较多。 --> <bean id="accountService" class="com.ac.service.impl.AccountServiceImpl"> <property name="name" value="test"></property> <property name="age" value="21"></property> <property name="birthday" ref="now"></property> </bean> <bean id="now" class="java.util.Date"></bean>
public class AccountServiceImpl implements IAccountService { private String name; private Integer age; private Date birthday; public void setName(String name) { this.name = name; } public void setAge(Integer age) { this.age = age; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override public void saveAccount() { System.out.println(name+","+age+","+birthday); } }
配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" 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"> <bean id="accountService" class="com.ac.service.impl.AccountServiceImpl" p:name="test" p:age="21" p:birthday-ref="now"/> </beans> <bean id="now" class="java.util.Date"></bean>
public class AccountServiceImpl implements IAccountService { private String[] myStrs; private List<String> myList; private Set<String> mySet; private Map<String,String> myMap; private Properties myProps; public void setMyStrs(String[] myStrs) { this.myStrs = myStrs; } public void setMyList(List<String> myList) { this.myList = myList; } public void setMySet(Set<String> mySet) { this.mySet = mySet; } public void setMyMap(Map<String, String> myMap) { this.myMap = myMap; } public void setMyProps(Properties myProps) { this.myProps = myProps; } @Override public void saveAccount() { System.out.println(Arrays.toString(myStrs)); System.out.println(myList); System.out.println(mySet); System.out.println(myMap); System.out.println(myProps); } }
配置文件:
<!-- 注入集合数据 List 结构的: array,list,set Map 结构的 map,entry,props,prop --> <bean id="accountService" class="com.ac.service.impl.AccountServiceImpl"> <!-- 在注入集合数据时,只要结构相同,标签可以互换 --> <!-- 给数组注入数据 --> <property name="myStrs"> <set> <value>AAA</value> <value>BBB</value> <value>CCC</value> </set> </property> <!-- 注入 list 集合数据 --> <property name="myList"> <array> <value>AAA</value> <value>BBB</value> <value>CCC</value> </array> </property> <!-- 注入 set 集合数据 --> <property name="mySet"> <list> <value>AAA</value> <value>BBB</value> <value>CCC</value> </list> </property> <!-- 注入 Map 数据 --> <property name="myMap"> <props> <prop key="testA">aaa</prop> <prop key="testB">bbb</prop> </props> </property> <!-- 注入 properties 数据 --> <property name="myProps"> <map> <entry key="testA" value="aaa"></entry> <entry key="testB"> <value>bbb</value></entry> </map> </property> </bean>
create table account( id int primary key auto_increment, name varchar(40), money double )character set utf8 collate utf8_general_ci; insert into account(name,money) values('zs',1000); insert into account(name,money) values('ls',1000); insert into account(name,money) values('ww',1000);
<dependencies> <!--数据源--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.14</version> </dependency> <!--DButils--> <dependency> <groupId>commons-dbutils</groupId> <artifactId>commons-dbutils</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> </dependencies>
public class Account implements Serializable{ private Integer id; private String name; private Double money; }
4.Mapper
public interface AccountMapper { /** * 保存 * @param account * @throws SQLException */ void save(Account account) throws SQLException; /** * 修改 * @param account * @throws SQLException */ void update(Account account) throws SQLException; /** * 删除 * @param id * @throws SQLException */ void delete(Integer id) throws SQLException; /** * 根据ID查找 * @param id * @return * @throws SQLException */ Account findById(Integer id) throws SQLException; /** * 查询所有 * @return * @throws SQLException */ List<Account> findAll() throws SQLException; }
public class AccountDaoImpl implements AccountDao { private QueryRunner queryRunner; /** * 属性注入QueryRunner对象 * @param queryRunner */ public void setQueryRunner(QueryRunner queryRunner) { this.queryRunner = queryRunner; } /** * 保存 * @param account * @throws SQLException */ @Override public void save(Account account) throws SQLException { //3. 执行sql语句 queryRunner.update("insert into values(?,?,?)",null,account.getName(),account.getMoney()); } /** * 修改 * @param account * @throws SQLException */ @Override public void update(Account account) throws SQLException { //3. 执行sql语句 queryRunner.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId()); } /** * 删除 * @param id * @throws SQLException */ @Override public void delete(Integer id) throws SQLException { //3. 执行sql语句 queryRunner.update("delete from account where id=?",id); } /** * 根据ID查找 * @param id * @return * @throws SQLException */ @Override public Account findById(Integer id) throws SQLException { //3. 执行sql语句 return queryRunner.query("select * from account where id=?",new BeanHandler<Account>(Account.class),id); } /** * 查询所有 * @return * @throws SQLException */ @Override public List<Account> findAll() throws SQLException { //3. 执行sql语句 return queryRunner.query("select * from account ",new BeanListHandler<Account>(Account.class)); } }
public interface AccountService { /** * 保存 * @param account */ void save(Account account) throws Exception; /** * 修改 * @param account */ void update(Account account) throws Exception; /** * 删除 * @param id */ void delete(Integer id) throws Exception; /** * 根据ID查找 * @param id * @return */ Account findById(Integer id) throws Exception; /** * 查询所有 * @return */ List<Account> findAll() throws Exception; }
public class AccountServicesImpl implements AccountServices { private AccountMapper accountMapper; public void setAccountMapperImpl(AccountMapper accountMapper) { this.accountMapper = accountMapper; } @Override public void save(Account account) throws Exception { accountMapper.save(account); } @Override public void update(Account account) throws Exception { accountMapper.update(account); } @Override public void delete(Integer id) throws Exception { accountMapper.delete(id); } @Override public Account findById(Integer id) throws Exception { return accountMapper.findById(id); } @Override public List<Account> findAll() throws Exception { return accountMapper.findAll(); } }
<?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"> <!--1.创建service--> <bean id="accountservices" class="com.ac.Services.Impl.AccountServicesImpl"> <!--2.注入Mapper-Set方式注入--> <property name="accountMapperImpl" ref="accountmapper"></property> </bean> <!--2.注入Mapper--> <bean id="accountmapper" class="com.ac.Mapper.Impl.AccountMapperImpl"> <!--注入2.1queryRunner--> <property name="queryRunner" ref="queryRunner" ></property> </bean> <!--注入queryRunner--> <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner"> <constructor-arg name="ds" ref="dataSource"></constructor-arg> </bean> <!--注入数据源--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8"/> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="username" value="root"/> <property name="password" value="root"/> </bean> </beans>
public class Test { /** * Spring集成DButils测试 * @throws Exception */ @org.junit.Test public void testFindAll() throws Exception { //创建容器 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); //获取AccountService实例 AccountServices accountService = (AccountServices) applicationContext.getBean("accountservices"); //调用findAll方法 List<Account> list = accountService.findAll(); System.out.println(list); } }
相对于相当于xml中:
作用:
把资源让 spring 来管理。相当于在 xml 中配置一个 bean。
属性:
value:指定 bean 的 id。如果不指定 value 属性,默认 bean 的 id 是当前类的类名。首字母小写。
继承@Component
他们三个注解都是针对一个的衍生注解,他们的作用及属性都是一模一样的。
他们只不过是提供了更加明确的语义化。
@Controller**:**一般用于表现层的注解。
@Service**:**一般用于业务层的注解。
@Repository**:**一般用于持久层的注解。
细节:如果注解中有且只有一个属性要赋值时,且名称是 value,value 在赋值是可以不写。
相当于xml中的:
作用: 自动按照类型注入。当使用注解注入属性时,set 方法可以省略。它只能注入其他 bean 类型。当有多个类型匹配时,使用要注入的对象变量名称作为 bean 的 id,在 spring 容器查找,找到了也可以注入成功。找不到 就报错。
作用: 在自动按照类型注入的基础之上,再按照 Bean 的 id 注入。它在给字段注入时不能独立使用,必须和 @Autowire 一起使用;但是给方法参数注入时,可以独立使用。 例如给方法中注入参数
属性: value:指定 bean 的 id。
作用: 直接按照 Bean 的 id 注入。它也只能注入其他 bean 类型。
属性: name:指定 bean 的 id。
作用: 注入基本数据类型和 String 类型数据的
属性: value:用于指定值
相当于XML中的:<bean id="" class="" scope="">
作用: 指定 bean 的作用范围。
属性: value:指定范围的值。 取值:singleton prototype request session globalsession
<bean id="" class="" init-method="" destroy-method="" />
作用: 用于指定初始化方法
作用: 用于指定销毁方法。
开启Spring 注解扫描的前提是,在Spring 的配置文件中开启注解扫描配置,如下
<context:component-scan base-package="com.ac"></context:component-scan> 开启注解扫描配置时需要注意的两点: 1.开启注解扫描需要扫描那些包(例如扫描特定的包) <!--示例1 use-default-filters="false" 表示现在不使用默认 filter,自己配置 filter context:include-filter ,设置扫描哪些内容 --> <context:component-scan base-package="com.ac" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> 2.开启注解扫描不需要扫描的包(例如整合SpringMVC的时候) <!--示例2 下面配置扫描包所有内容 context:exclude-filter: 设置哪些内容不进行扫描 --> <context:component-scan base-package="com.ac"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
作用: 用于指定当前类是一个 spring 配置类,当创建容器时会从该类上加载注解。获取容器时需要使用
AnnotationApplicationContext(有@Configuration 注解的类.class)。
属性: value:用于指定配置类的字节码
@Configuration @ComponentScan(basePackages={"com.ac"}) @Import(value = {JdbcConfig.class}) public class SpringConfig { }
作用: 用于指定 spring 在初始化容器时要扫描的包。作用和在 spring 的 xml 配置文件中的:
<context:component-scan base-package=“com.ac”/>是一样的。
属性: basePackages:用于指定要扫描的包。和该注解中的 value 属性作用一样。
@PropertySource(value={"classpath:jdbcConfig.properties"}) public class JdbcConfig { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; @Bean(name="queryRunner") public QueryRunner createQueryRunner(@Qualifier(value="dataSource") DataSource dataSource){ return new QueryRunner(dataSource); } /** * 用于创建DataSource * @return * @throws PropertyVetoException */ @Bean(name = "dataSource") public DataSource createDataSource() throws PropertyVetoException { DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName(driver); ds.setUrl(url); ds.setUsername(username); ds.setPassword(password); return ds; } } ====================================================================== jdbcConfig.properties: jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8 jdbc.username=root jdbc.password=root
作用: 该注解只能写在方法上,表明使用此方法创建一个对象,并且放入 spring 容器。返回值为创建对象的类型
属性: name:给当前@Bean 注解方法创建的对象指定一个名称(即 bean 的 id)。
**作用:**用于加载.properties 文件中的配置。例如我们配置数据源时,可以把连接数据库的信息写到 properties 配置文件中,就可以使用此注解指定 properties 配置文件的位置。
属性: value[]:用于指定 properties 文件位置。如果是在类路径下,需要写上 classpath: 例如:@PropertySource(value={“classpath:jdbcConfig.properties”})
作用: 用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration 注解。当然,写上也没问题。
属性: value[]:用于指定其他配置类的字节码
1.引入MAVEN
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.2.RELEASE</version> </dependency>
2.使用**@RunWith** 注解替换原有运行器
3.使用**@ContextConfiguration** 指定 spring 配置文件的位置
@ContextConfiguration 注解:
locations **属性:**用于指定配置文件的位置。如果是类路径下,需要用 **classpath:**表明
classes **属性:**用于指定注解的类。当不使用 xml 配置时,需要用此属性指定注解类的位置。
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes ={SpringConfig.class} ) public class Test { @Autowired private AccountServices accountservices ; /** * Spring集成DButils测试 * @throws Exception */ @org.junit.Test public void testFindAll() throws Exception { //创建容器 /* ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");*/ // ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class); //获取AccountService实例 // AccountServices accountService = // (AccountServices) applicationContext.getBean("accountservices"); //调用findAll方法 List<Account> list = accountservices.findAll(); System.out.println(list); } }
全称是AspectOriented Programming, 即面向切面编程。在不修改源码的基础上,对我们的已有方法进行增强。
基于接口的动态代理 提供者:JDK 官方的 Proxy 类。 要求:被代理类最少实现一个接口。
1.1创建生产类
public interface Producer { String save(float Money); } =============================================================================== public class ProducerImpl implements Producer { //重写生产者 @Override public String save(float Money) { return "生产者获取的数量==="+Money; } }
1.2创建消费者以及代理消费
public class Consumer { public static void main(String[] args) { // 消费者获取生产者 final Producer producer = new ProducerImpl(); String save = producer.save(10000f); System.out.println(save); /** * 动态代理: * 特点:字节码随用随创建,随用随加载 * 作用:不修改源码的基础上对方法增强 * 分类: * 基于接口的动态代理 * 基于子类的动态代理 * 基于接口的动态代理: * 涉及的类:Proxy * 提供者:JDK官方 * 如何创建代理对象: * 使用Proxy类中的newProxyInstance方法 * 创建代理对象的要求: * 被代理类最少实现一个接口,如果没有则不能使用 * newProxyInstance方法的参数: * ClassLoader:类加载器 * 它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。 * Class[]:字节码数组 * 它是用于让代理对象和被代理对象有相同方法。固定写法。 * InvocationHandler:用于提供增强的代码 * 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。 * 此接口的实现类都是谁用谁写。 */ //创建代理对象 Producer producer1=(Producer)Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() { /** * 作用:执行被代理对象的任何接口方法都会经过该方法 * 方法参数的含义 * @param proxy 代理对象的引用 --就是代理对象的本身 * @param method 当前执行的方法 * @param args 当前执行方法所需的参数 * @return 和被代理对象方法有相同的返回值 * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object returnValue=null; Float monet= (Float) args[0]; if ("save".equals(method.getName())){ returnValue=method.invoke(producer,monet*0.8f); } return returnValue; } }); System.out.println(producer1.save(10000f)); }
基于子类的动态代理
提供者:第三方的 CGLib,如果报 asmxxxx 异常,需要导入 asm.jar。
要求:被代理类不能用 final 修饰的类(最终类)。
2.0引入jar包
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.1_3</version> </dependency>
2.1创建生产者
public class ProducerImpl { //重写生产者 public String save(float Money) { return "生产者获取的数量==="+Money; } }
2.2创建消费者
public class Consumer { public static void main(String[] args) { // 消费者获取生产者 ProducerImpl producer = new ProducerImpl(); String save = producer.save(10000f); System.out.println(save); // 创建代理者 /** * 基于子类的动态代理 * 要求: * 被代理对象不能是最终类 * 用到的类: * Enhancer * 用到的方法: * create(Class, Callback) * 方法的参数: * Class:被代理对象的字节码 * Callback:如何代理 * @param args */ ProducerImpl producer1=(ProducerImpl)Enhancer.create(ProducerImpl.class, new MethodInterceptor() { /** * 执行被代理对象的任何方法,都会经过该方法。在此方法内部就可以对被代理对象的任何 方法进行增强。 * * 参数: * 前三个和基于接口的动态代理是一样的。 * MethodProxy:当前执行方法的代理对象。 * 返回值: * 当前执行方法的返回值 */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { Object returnValue=null; Float monet= (Float) objects[0]; if ("save".equals(method.getName())){ returnValue=method.invoke(producer,monet*0.8f); } return returnValue; } }); System.out.println(producer1.save(20000f)); } }
JoinPoint: 连接点
类里面哪些方法可以被增强,这些方法称为连接点. 在spring的AOP中,指的是所有现有的方法。
Pointcut: 切入点
在类里面可以有很多方法被增强,但是实际开发中,我们只对具体的某几个方法而已,那么这些实际增强的方法就称之为切入点
Advice: 通知/增强
增强的逻辑、称为增强,比如给某个切入点(方法) 扩展了校验权限的功能,那么这个校验权限即可称之为增强 或者是通知
通知分为:
前置通知: 在原来方法之前执行. 后置通知: 在原来方法之后执行. 特点: 可以得到被增强方法的返回值 环绕通知:在方法之前和方法之后执行. 特点:可以阻止目标方法执行 异常通知: 目标方法出现异常执行. 如果方法没有异常,不会执行. 特点:可以获得异常的信息 最终通知: 指的是无论是否有异常,总是被执行的。
Aspect: 切面
切入点和通知的结合。
public interface AccountDao { /** * 1.连接点:所有方法 * 2.切入点:需要被增强的方法; * 例如:调用save()方法之前,增加权限校验功能,此时save()方法就是切入点 * 3.通知:增强的逻辑,这里是强调增强的时间点; * 例如:在调用save()方法前,进行开启事务功能增强,称为前置通知 * 在调用save()方法后,没有异常,进行事务提交功能增强,称之为后置通知 * 在调用save()方法时,发生异常,进行事务回滚功能增强,称之为异常通知 * 在调用save()方法,无论发生什么事情,一定要执行日志记录功能,称之为最终通知,相当于finally * 控制调用save()方法的整个过程,称之为环绕通知 * 4.切面:把切入点和通知结合;说白了就是把通知的逻辑用在切入点里面 */ void save(); void update(); void delete(); void findById(); void findAll(); }
切面类:属于通知的逻辑功能,每个方法叫增强方法
public class TransactionManager { /** * 前置通知 */ public void beginTransaction(){ System.out.println("1.开启事务"); } /*** * 后置通知 */ public void commit(){ System.out.println("2.提交事务"); } /** * 异常通知 */ public void rollback(){ System.out.println("3.异常通知"); } /*** * 最终通知 */ public void finallyMethod(){ System.out.println("4.资源回收,finally"); } }
4.1创建Maven
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>commons-dbutils</groupId> <artifactId>commons-dbutils</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.14</version> </dependency> <!--SpringAOP相关的坐标--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.7</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.0.2.RELEASE</version> </dependency> <!--Spring整合单元测试--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.2.RELEASE</version> </dependency> <!--单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.23</version> </dependency> </dependencies>
4.2创建pojo
public class Account implements Serializable { private Integer id; private String name; private Double money; public Account(Integer id, String name, Double money) { this.id = id; this.name = name; this.money = money; } public Account() { } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Double getMoney() { return money; } public void setMoney(Double money) { this.money = money; } @Override public String toString() { return "Account{" + "id=" + id + ", name='" + name + '\'' + ", money=" + money + '}'; } }
4.3创建Mapper
AccountMapper.java
public interface AccountMapper { /** * 查询所有 * @return */ List<Account> findAllAccount(); /** * 查询一个 * @return */ Account findAccountById(Integer accountId); /** * 保存 * @param account */ void saveAccount(Account account); /** * 更新 * @param account */ void updateAccount(Account account); /** * 删除 * @param acccountId */ void deleteAccount(Integer acccountId); /** * 根据名称查询账户 * @param accountName * @return 如果有唯一的一个结果就返回,如果没有结果就返回null * 如果结果集超过一个就抛异常 */ Account findAccountByName(String accountName); }
AccountMapperImpl.java
public class AccountMapperImpl implements AccountMapper { private QueryRunner runner; private ConnectionUtils connectionUtils; public void setRunner(QueryRunner runner) { this.runner = runner; } public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } @Override public List<Account> findAllAccount() { try{ return runner.query(connectionUtils.getThreadConnection(),"select * from account",new BeanListHandler<Account>(Account.class)); }catch (Exception e) { throw new RuntimeException(e); } } @Override public Account findAccountById(Integer accountId) { try{ return runner.query(connectionUtils.getThreadConnection(),"select * from account where id = ? ",new BeanHandler<Account>(Account.class),accountId); }catch (Exception e) { throw new RuntimeException(e); } } @Override public void saveAccount(Account account) { try{ runner.update(connectionUtils.getThreadConnection(),"insert into account(name,money)values(?,?)",account.getName(),account.getMoney()); }catch (Exception e) { throw new RuntimeException(e); } } @Override public void updateAccount(Account account) { try{ runner.update(connectionUtils.getThreadConnection(),"update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId()); }catch (Exception e) { throw new RuntimeException(e); } } @Override public void deleteAccount(Integer accountId) { try{ runner.update(connectionUtils.getThreadConnection(),"delete from account where id=?",accountId); }catch (Exception e) { throw new RuntimeException(e); } } @Override public Account findAccountByName(String accountName) { try{ List<Account> accounts = runner.query(connectionUtils.getThreadConnection(),"select * from account where name = ? ",new BeanListHandler<Account>(Account.class),accountName); if(accounts == null || accounts.size() == 0){ return null; } if(accounts.size() > 1){ throw new RuntimeException("结果集不唯一,数据有问题"); } return accounts.get(0); }catch (Exception e) { throw new RuntimeException(e); } } }
4.4创建Service
AccountServices.java
public interface AccountServices { /** * 查询所有 * @return */ List<Account> findAllAccount(); /** * 查询一个 * @return */ Account findAccountById(Integer accountId); /** * 保存 * @param account */ void saveAccount(Account account); /** * 更新 * @param account */ void updateAccount(Account account); /** * 删除 * @param acccountId */ void deleteAccount(Integer acccountId); /** * 转账 * @param sourceName 转出账户名称 * @param targetName 转入账户名称 * @param money 转账金额 */ void transfer(String sourceName,String targetName,Float money); }
AccountServicesImpl.java
public class AccountServicesImpl implements AccountServices { private AccountMapper accountMapper; public void setAccountMapperImpl(AccountMapper accountMapper) { this.accountMapper = accountMapper; } @Override public List<Account> findAllAccount() { return accountMapper.findAllAccount(); } @Override public Account findAccountById(Integer accountId) { return accountMapper.findAccountById(accountId); } @Override public void saveAccount(Account account) { accountMapper.saveAccount(account); } @Override public void updateAccount(Account account) { accountMapper.updateAccount(account); } @Override public void deleteAccount(Integer acccountId) { accountMapper.deleteAccount(acccountId); } @Override public void transfer(String sourceName, String targetName, Float money) { System.out.println("transfer...."); //2.1根据名称查询转出账户 Account source = accountMapper.findAccountByName(sourceName); //2.2根据名称查询转入账户 Account target = accountMapper.findAccountByName(targetName); //2.3转出账户减钱 source.setMoney(source.getMoney()-money); //2.4转入账户加钱 target.setMoney(target.getMoney()+money); //2.5更新转出账户 accountMapper.updateAccount(source); int i=1/0; //2.6更新转入账户 accountMapper.updateAccount(target); } }
4.5创建工具
ConnectionUtils.java
public class ConnectionUtils { private ThreadLocal<Connection> threadLocal= new ThreadLocal<Connection>(); private DataSource dataSource; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } // 获取链接 public Connection getThreadConnection() { try{ //1.先从ThreadLocal上获取 Connection conn = threadLocal.get(); //2.判断当前线程上是否有连接 if (conn == null) { //3.从数据源中获取一个连接,并且存入ThreadLocal中 conn = dataSource.getConnection(); threadLocal.set(conn); } //4.返回当前线程上的连接 return conn; }catch (Exception e){ throw new RuntimeException(e); } }; // 释放连接和线程的关系 /** * 把连接和线程解绑 */ public void removeConnection(){ threadLocal.remove(); } }
4.6创建切面类
public class TransactionManager { private ConnectionUtils connectionUtils; public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } /** * 开启事务 */ public void beginTransaction(){ try { connectionUtils.getThreadConnection().setAutoCommit(false); }catch (Exception e){ e.printStackTrace(); } } /** * 提交事务 */ public void commit(){ try { connectionUtils.getThreadConnection().commit(); }catch (Exception e){ e.printStackTrace(); } } /** * 回滚事务 */ public void rollback(){ try { connectionUtils.getThreadConnection().rollback(); }catch (Exception e){ e.printStackTrace(); } } /** * 释放连接 */ public void release(){ try { connectionUtils.getThreadConnection().close();//还回连接池中 connectionUtils.removeConnection(); }catch (Exception e){ e.printStackTrace(); } } }
4.4创建配置文件: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" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--注入services--> <bean id="accountservices" class="com.ac.Services.Impl.AccountServicesImpl"> <!--注入mapper--> <property name="accountMapperImpl" ref="accountMapper"></property> </bean> <!--注入Mapper--> <bean id="accountMapper" class="com.ac.Mapper.Impl.AccountMapperImpl"> <!--注入QueryRunner--> <property name="runner" ref="queryRunner"></property> <!--注入Connection--> <property name="connectionUtils" ref="connectionutile"></property> </bean> <!--注入QueryRunner--> <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner"> <!--注入数据源--> <constructor-arg name="ds" ref="DruiddataSource"></constructor-arg> </bean> <!--注入数据源--> <bean id="DruiddataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8"/> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="username" value="root"/> <property name="password" value="root"/> </bean> <!-- 配置数据源 --> <bean id="c3p0dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!--连接数据库的必备信息--> <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8"></property> <property name="user" value="root"></property> <property name="password" value="root"></property> </bean> <!--配置切面类--> <!--1.配置增强方法--> <bean id="txManger" class="com.ac.Util.TransactionManager"> <!--注入连接--> <property name="connectionUtils" ref="connectionutile"></property> </bean> <bean id="connectionutile" class="com.ac.Util.ConnectionUtils"> <!--注入数据源--> <property name="dataSource" ref="DruiddataSource"></property> </bean> <!--设置切面 aop:config: 作用:用于声明开始 aop 的配置 aop:aspect: 作用: 用于配置切面。 属性: id:给切面提供一个唯一标识。 ref:引用配置好的通知类 bean 的 id。 aop:pointcut: 作用: 用于配置切入点表达式。就是指定对哪些类的哪些方法进行增强。 属性: expression:用于定义切入点表达式。 id:用于给切入点表达式提供一个唯一标识 aop:before 作用: 用于配置前置通知。指定增强的方法在切入点方法之前执行 属性: method:用于指定通知类中的增强方法名称 ponitcut-ref:用于指定切入点的表达式的引用 poinitcut:用于指定切入点表达式 执行时间点: 切入点方法执行之前执行 aop:after-returning 作用: 用于配置后置通知 属性: method:指定通知中方法的名称。 pointct:定义切入点表达式 pointcut-ref:指定切入点表达式的引用 执行时间点: 切入点方法正常执行之后。它和异常通知只能有一个执行 aop:after-throwing 作用: 用于配置异常通知 属性: method:指定通知中方法的名称。 pointct:定义切入点表达式 pointcut-ref:指定切入点表达式的引用 执行时间点: 切入点方法执行产生异常后执行。它和后置通知只能执行一个 aop:after 作用: 用于配置最终通知 属性: method:指定通知中方法的名称。 pointct:定义切入点表达式 pointcut-ref:指定切入点表达式的引用 执行时间点: 无论切入点方法执行时是否有异常,它都会在其后面执行。 aop:around: 作用: 用于配置环绕通知 属性: method:指定通知中方法的名称。 pointct:定义切入点表达式 pointcut-ref:指定切入点表达式的引用 说明: 它是 spring 框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。 注意: 通常情况下,环绕通知都是独立使用的 --> <aop:config> <aop:pointcut id="pt1" expression="execution(* com.com.ac.Services.Impl.*.*(..))"/> <aop:aspect id="transaction" ref="txManger"> <!--配置前置通知--> <aop:before method="beginTransaction" pointcut-ref="pt1"></aop:before> <!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个--> <aop:after-returning method="commit" pointcut-ref="pt1"></aop:after-returning> <!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个--> <aop:after-throwing method="rollback" pointcut-ref="pt1"></aop:after-throwing> <!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行--> <aop:after method="release" pointcut-ref="pt1"></aop:after> </aop:aspect> </aop:config> </beans>
环绕通知一般通过编写代码进行设置
/** * 环绕通知 * 问题: * 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。 * 分析: * 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。 * 解决: * Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。 * 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。 * * spring中的环绕通知: * 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。 */ public Object aroundPringLog(ProceedingJoinPoint pjp){ Object rtValue = null; try{ Object[] args = pjp.getArgs();//得到方法执行所需的参数 System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置"); rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法) List<Object> asList = Arrays.asList(args);//参数 Signature signature = pjp.getSignature(); String name = signature.getName(); //方法名称 System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置"+name+"========="+asList ); return rtValue; }catch (Throwable t){ System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常"); throw new RuntimeException(t); }finally { System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终"); } }
切入点表达式
1.引入jar包
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.7</version> </dependency>
拷贝上面的
1.开启注解扫描和AOP支持
<context:component-scan base-package="com.ac"></context:component-scan> <!-- 开启 spring 对注解 AOP 的支持 --> <aop:aspectj-autoproxy/>
2.把类加入Spring 管理
3.增加切面类注解
@Aspect//表明当前类是一个切面类
4.在增强方法上进行注解配置
@Before 作用:把当前方法看成是前置通知。 属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用 @AfterReturning 作用:把当前方法看成是后置通知。 属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用 @AfterThrowing 作用:把当前方法看成是异常通知。 属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用 @After 作用:把当前方法看成是最终通知。 属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用 @Around 作用:把当前方法看成是环绕通知。 属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用 @Pointcut 作用:指定切入点表达式 属性:value:指定表达式的内容 @Pointcut("execution(* com.ac.service.impl.*.*(..))") private void pt1() {}
使用纯注解:在配置类上增加:@EnableAspectJAutoProxy
(1) 切入点表达式作用:知道对哪个类里面的哪个方法进行增强
(2) 语法结构: execution([权限修饰符] [返回类型] [类全路径] 方法名称 )
execution:匹配方法的执行(常用) execution(表达式) 表达式语法:execution([修饰符] 返回值类型 包名.类名.方法名(参数)) 写法说明: 全匹配方式: public void com.ac.service.impl.AccountServiceImpl.saveAccount(com.ac.domain.Account) 访问修饰符可以省略 void com.ac.service.impl.AccountServiceImpl.saveAccount(com.ac.domain.Account) 返回值可以使用*号,表示任意返回值 * com.ac.service.impl.AccountServiceImpl.saveAccount(com.ac.domain.Account) 包名可以使用*号,表示任意包,但是有几级包,需要写几个* * *.*.*.*.AccountServiceImpl.saveAccount(com.ac.domain.Account) 使用..来表示当前包,及其子包 * com..AccountServiceImpl.saveAccount(com.ac.domain.Account) 类名可以使用*号,表示任意类 * com..*.saveAccount(com.ac.domain.Account) 方法名可以使用*号,表示任意方法 * com..*.*( com.ac.domain.Account) 参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数 * com..*.*(*) 参数列表可以使用..表示有无参数均可,有参数可以是任意类型 * com..*.*(..) 全通配方式: * *..*.*(..) 注: 通常情况下,我们都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类。 execution(* com.ac.service.impl.*.*(..))
1.创建Maven
<dependencies> <!--Spring核心容器--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> <!--SpringJdbc--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.0.2.RELEASE</version> </dependency> <!--事务相关的--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.23</version> </dependency> <!--Spring整合单元测试--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.2.RELEASE</version> </dependency> <!--单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.14</version> </dependency> </dependencies>
2.编写Mapper
AccountMapper.java
public interface AccountMapper { //增加 boolean save(Account account); //修改 boolean update(Account account); //删除 boolean delete(String id); //查询单个 Account findone(String id); //查询多个 List<Account> findAll(); }
AccountMapperImpl.java
public class AccountMapperImpl implements AccountMapper { // 注入JdbcTemplate private JdbcTemplate jdbcTemplate; public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @Override public boolean save(Account account) { String sql3 = "insert into account values(?,?,?)"; int update = jdbcTemplate.update(sql3, null, account.getName(), account.getMoney()); if (update>0){ return true; } return false; } @Override public boolean update(Account account) { String sql4 ="update account set name = ? where id = ?"; Object[] objects = {account.getName(), account.getId()}; int update = jdbcTemplate.update(sql4, objects); if (update>0){ return true; } return false; } @Override public boolean delete(String id) { String sql5 = "delete from account where id=?"; int update = jdbcTemplate.update(sql5, id); if (update>0){ return true; } return false; } @Override public Account findone(String id) { String sql2="select * from account where id=?"; Account account = jdbcTemplate.queryForObject(sql2, new BeanPropertyRowMapper<>(Account.class), id); return account; } @Override public List<Account> findAll() { String sql = "select * from account"; List<Account> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Account>(Account.class)); return list; } }
3.编写配置文件 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"> <!--注入Mapper--> <bean id="accountMapper" class="com.ac.Mapper.Impl.AccountMapperImpl"> <property name="jdbcTemplate" ref="jdbctemplate"></property> </bean> <!--注入JdbcTemplate--> <bean id="jdbctemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <constructor-arg name="dataSource" ref="DruiddataSource"></constructor-arg> </bean> <!--注入数据源--> <bean id="DruiddataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8"/> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="username" value="root"/> <property name="password" value="root"/> </bean> <!-- 配置数据源 --> <bean id="c3p0dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!--连接数据库的必备信息--> <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8"></property> <property name="user" value="root"></property> <property name="password" value="root"></property> </bean> </beans>
Dao 使用方式二
public class AccountMapperImpl extends JdbcDaoSupport implements AccountMapper { @Override public boolean save(Account account) { String sql3 = "insert into account values(?,?,?)"; int update = super.getJdbcTemplate().update(sql3, null, account.getName(), account.getMoney()); if (update>0){ return true; } return false; } @Override public boolean update(Account account) { String sql4 ="update account set name = ? where id = ?"; Object[] objects = {account.getName(), account.getId()}; int update = super.getJdbcTemplate().update(sql4, objects); if (update>0){ return true; } return false; } @Override public boolean delete(String id) { String sql5 = "delete from account where id=?"; int update = super.getJdbcTemplate().update(sql5, id); if (update>0){ return true; } return false; } @Override public Account findone(String id) { String sql2="select * from account where id=?"; Account account = super.getJdbcTemplate().queryForObject(sql2, new BeanPropertyRowMapper<>(Account.class), id); return account; } @Override public List<Account> findAll() { String sql = "select * from account"; List<Account> list = super.getJdbcTemplate().query(sql, new BeanPropertyRowMapper<Account>(Account.class)); return list; } }
相关的API
PlatformTransactionManager
平台事务管理器是一个接口,实现类就是Spring真正管理事务的对象。
常用的实现类:
DataSourceTransactionManager :JDBC开发的时候,使用事务管理。
HibernateTransactionManager :Hibernate开发的时候,使用事务管理。
TransactionDefinition
事务定义信息中定义了事务的隔离级别,传播行为,超时信息,只读。
TransactionStatus:事务的状态信息
事务状态信息中定义事务是否是新事务,是否有保存点。
1.pojo
public class Account { private Integer id; private String name; private Double money; //...set..get..toString }
2.Mapper
AccountMapper.java
public interface AccountMapper { //增加 boolean save(Account account); //修改 boolean update(Account account); //删除 boolean delete(String id); //查询单个 Account findone(String id); //查询多个 List<Account> findAll(); //根据名称查询账户 Account findAccountByName(String accountName); }
AccountMapperImpl.java
public class AccountMapperImpl implements AccountMapper { // 注入JdbcTemplate private JdbcTemplate jdbcTemplate; public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @Override public boolean save(Account account) { String sql3 = "insert into account values(?,?,?)"; int update = jdbcTemplate.update(sql3, null, account.getName(), account.getMoney()); if (update>0){ return true; } return false; } @Override public boolean update(Account account) { String sql4 ="update account set name=?,money=? where id=?"; Object[] objects = {account.getName(),account.getMoney(), account.getId()}; int update = jdbcTemplate.update(sql4, objects); if (update>0){ return true; } return false; } @Override public boolean delete(String id) { String sql5 = "delete from account where id=?"; int update = jdbcTemplate.update(sql5, id); if (update>0){ return true; } return false; } @Override public Account findone(String id) { String sql2="select * from account where id=?"; Account account = jdbcTemplate.queryForObject(sql2, new BeanPropertyRowMapper<>(Account.class), id); return account; } @Override public List<Account> findAll() { String sql = "select * from account"; List<Account> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Account>(Account.class)); return list; } @Override public Account findAccountByName(String accountName) { try{ List<Account> accounts = jdbcTemplate.query("select * from account where name = ? ",new BeanPropertyRowMapper<Account>(Account.class),accountName); if(accounts == null || accounts.size() == 0){ return null; } if(accounts.size() > 1){ throw new RuntimeException("结果集不唯一,数据有问题"); } return accounts.get(0); }catch (Exception e) { throw new RuntimeException(e); } } }
3.service
AccountService.java
public interface AccountService { /** * 转账 * @param sourceName 转出账户名称 * @param targeName 转入账户名称 * @param money 转账金额 */ void transfer(String sourceName,String targeName,Float money);//增删改 void transfer2(String sourceName,String targeName,Float money);//增删改 }
AccountServiceImpl.java
public class AccountServiceImpl implements AccountService { //注入Mapper private AccountMapper accountMapper; public void setAccountMapper(AccountMapper accountMapper) { this.accountMapper = accountMapper; } @Override public void transfer(String sourceName, String targeName, Float money) { // 1.根据名称查询两个账户 Account source = accountMapper.findAccountByName(sourceName); Account target = accountMapper.findAccountByName(targeName); //2.修改两个账户的金额 source.setMoney(source.getMoney()-money);//转出账户减钱 target.setMoney(target.getMoney()+money);//转入账户加钱 //3.更新两个账户 accountMapper.update(source); // int i=1/0; accountMapper.update(target); } }
4.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"> <!--注入services--> <bean id="accountService" class="com.ac.Service.Impl.AccountServiceImpl"> <property name="accountMapper" ref="accountMapper"></property> </bean> <!--注入Mapper--> <bean id="accountMapper" class="com.ac.Mapper.Impl.AccountMapperImpl"> <property name="jdbcTemplate" ref="jdbctemplate"></property> </bean> <!--注入JdbcTemplate--> <bean id="jdbctemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <constructor-arg name="dataSource" ref="DruiddataSource"></constructor-arg> </bean> <!--注入数据源--> <bean id="DruiddataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8"/> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="username" value="root"/> <property name="password" value="root"/> </bean> <!-- 配置数据源 --> <bean id="c3p0dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!--连接数据库的必备信息--> <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8"></property> <property name="user" value="root"></property> <property name="password" value="root"></property> </bean> </beans>
1.修改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"> <!--注入事务管理器--> <bean id="transaction" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--注入数据源--> <property name="dataSource" ref="DruiddataSource"></property> </bean> <!--创建事务管理器模板对象--> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <!--注入事务管理器--> <constructor-arg name="transactionManager" ref="transaction"></constructor-arg> </bean> <!--注入services--> <bean id="accountService" class="com.ac.Service.Impl.AccountServiceImpl"> <property name="accountMapper" ref="accountMapper"></property> <!--注入事务管理模板对象--> <property name="transactionTemplate" ref="transactionTemplate"></property> </bean> <!--注入Mapper--> <bean id="accountMapper" class="com.ac.Mapper.Impl.AccountMapperImpl"> <property name="jdbcTemplate" ref="jdbctemplate"></property> </bean> <!--注入JdbcTemplate--> <bean id="jdbctemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <constructor-arg name="dataSource" ref="DruiddataSource"></constructor-arg> </bean> <!--注入数据源--> <bean id="DruiddataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8"/> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="username" value="root"/> <property name="password" value="root"/> </bean> <!-- 配置数据源 --> <bean id="c3p0dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!--连接数据库的必备信息--> <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8"></property> <property name="user" value="root"></property> <property name="password" value="root"></property> </bean> </beans>
2.修改services方法,注入事务管理模板,重写execute() 方法
public class AccountServiceImpl implements AccountService { //注入Mapper private AccountMapper accountMapper; public void setAccountMapper(AccountMapper accountMapper) { this.accountMapper = accountMapper; } // 注入事务管理 private TransactionTemplate transactionTemplate; public void setTransactionTemplate(TransactionTemplate transactionTemplate) { this.transactionTemplate = transactionTemplate; } @Override public void transfer(String sourceName, String targeName, Float money) { // 使用编程式事务进行查询控制 transactionTemplate.execute(new TransactionCallback<Object>() { @Override public Object doInTransaction(TransactionStatus transactionStatus) { // /1.根据名称查询两个账户 Account source = accountMapper.findAccountByName(sourceName); Account target = accountMapper.findAccountByName(targeName); //2.修改两个账户的金额 source.setMoney(source.getMoney()-money);//转出账户减钱 target.setMoney(target.getMoney()+money);//转入账户加钱 //3.更新两个账户 accountMapper.update(source); // int i=1/0; accountMapper.update(target); return null; } }); } }
修改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" 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/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 "> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8"/> <property name="user" value="root"/> <property name="password" value="root"/> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="accountDao" class="com.ac.dao.impl.AccountDaoImpl"> <property name="jdbcTemplate" ref="jdbcTemplate"/> </bean> <!--目标对象 内部的方法就是切点--> <bean id="accountService" class="com.ac.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"/> </bean> <!--配置平台事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!--通知 事务的增强--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!--设置事务的属性信息的--> <tx:attributes> <!-- read-only:是否是只读事务。默认false,不只读。 timeout:指定超时时间。默认值为:-1。永不超时。 rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。没有默认值,任何异常都回滚。 no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回滚。没有默认值,任何异常都回滚。 propagation:事务的传播行为 REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选 择(默认值) SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务) MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常 REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。 NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起 NEVER:以非事务方式运行,如果当前存在事务,抛出异常 NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作。 isolation:事务的隔离级别 1. ISOLATION_DEFAULT: 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别. 另外四个与JDBC的隔离级别相对应 2. ISOLATION_READ_UNCOMMITTED: 这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。 3. ISOLATION_READ_COMMITTED: 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据 4. ISOLATION_REPEATABLE_READ: 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。 它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。 5. ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。 除了防止脏读,不可重复读外,还避免了幻像读。 什么是脏数据,脏读,不可重复读,幻觉读? 脏读: 指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据, 那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。 不可重复读: 指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据 可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。 幻觉读: 指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么, 以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。 --> <tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/> <tx:method name="save" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/> <tx:method name="findAll" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true"/> <tx:method name="update*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true"/> <tx:method name="*"/> </tx:attributes> </tx:advice> <!--配置事务的aop织入--> <aop:config> <aop:pointcut id="txPointcut" expression="execution(* com.ac.service.impl.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/> </aop:config> </beans>
propagation:事务的传播行为
REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选
择(默认值)
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
NEVER:以非事务方式运行,如果当前存在事务,抛出异常
NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作。
1: PROPAGATION_REQUIRED
加入当前正要执行的事务不在另外一个事务里,那么就起一个新的事务
比如说,ServiceB.methodB的事务级别定义为PROPAGATION_REQUIRED, 那么由于执行ServiceA.methodA的时候,ServiceA.methodA已经起了事务,这时调用ServiceB.methodB,ServiceB.methodB看到自己已经运行在ServiceA.methodA的事务内部,就不再起新的事务。而假如ServiceA.methodA运行的时候发现自己没有在事务中,他就会为自己分配一个事务。这样,在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常,事务都会被回滚。即使ServiceB.methodB的事务已经被
提交,但是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚
2: PROPAGATION_SUPPORTS
如果当前在事务中,即以事务的形式运行,如果当前不再一个事务中,那么就以非事务的形式运行
3: PROPAGATION_MANDATORY
必须在一个事务中运行。也就是说,他只能被一个父事务调用。否则,他就要抛出异常
4: PROPAGATION_REQUIRES_NEW
这个就比较绕口了。 比如我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW,那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,ServiceB.methodB会起一个新的事务,等待ServiceB.methodB的事务完成以后,他才继续执行。他与PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为ServiceB.methodB是新起一个事务,那么就是存在两个不同的事务。如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB是不会回滚的。如果ServiceB.methodB失败回滚,如果他抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。
5: PROPAGATION_NOT_SUPPORTED
当前不支持事务。比如ServiceA.methodA的事务级别是PROPAGATION_REQUIRED ,而ServiceB.methodB的事务级别是PROPAGATION_NOT_SUPPORTED ,那么当执行到ServiceB.methodB时,ServiceA.methodA的事务挂起,而他以非事务的状态运行完,再继续ServiceA.methodA的事务。
6: PROPAGATION_NEVER
不能在事务中运行。假设ServiceA.methodA的事务级别是PROPAGATION_REQUIRED, 而ServiceB.methodB的事务级别是PROPAGATION_NEVER ,那么ServiceB.methodB就要抛出异常了。
7: PROPAGATION_NESTED
理解Nested的关键是savepoint。他与PROPAGATION_REQUIRES_NEW的区别是,PROPAGATION_REQUIRES_NEW另起一个事务,将会与他的父事务相互独立,而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的。
而Nested事务的好处是他有一个savepoint。
isolation:事务的隔离级别
1. ISOLATION_DEFAULT: 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.
另外四个与JDBC的隔离级别相对应
ISOLATION_READ_UNCOMMITTED: 这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
ISOLATION_READ_COMMITTED: 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据
ISOLATION_REPEATABLE_READ: 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。 它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。 除了防止脏读,不可重复读外,还避免了幻像读。
什么是脏数据,脏读,不可重复读,幻觉读?
脏读: 指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据, 那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。
不可重复读: 指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据 可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
幻觉读: 指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么, 以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。
1.修改applicationContext.xml
<!--1.配置平台事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!--2.二配置事务注解 --> <tx:annotation-driven transaction-manager="transactionManager"/>
2.在业务逻辑类上面使用注解
@Transactional : 如果在类上声明,那么标记着该类中的所有方法都使用事务管理。也可以作用于方法上,那么这就表示只有具体的方法才会使用事务。
注解型事务的参数类型以及相关意思:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3ZJCIUx1-1638109011016)(E:\IDEAWorkSpace\Frame_SSM\exercise-spring\Spring总结.assets\image-20211128220626843.png)]
(1) : propagation:事务传播行为
(1)多事务方法直接进行调用,这个过程中事务 是如何进行管理的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4tcU49YO-1638109011022)(E:\IDEAWorkSpace\Frame_SSM\exercise-spring\Spring总结.assets\image-20211128220738769.png)]
使用方式:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rX1iUarA-1638109011024)(E:\IDEAWorkSpace\Frame_SSM\exercise-spring\Spring总结.assets\image-20211128220808760.png)]
(2) : ioslation:事务隔离级别
(1) 事务有特性成为隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题
(2) 有三个读问题:脏读、不可重复读、虚(幻)读
(1)脏读:一个未提交事务读取到另一个未提交事务的数据
(2)不可重复读:一个未提交事务读取到另一个事务修改的数据
(3)幻读:一个未提交事务读取到另一提交事务添加数据
使用事务的隔离级别可以解决脏读,幻读,不可重复读
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JBggJEOw-1638109011025)(E:\IDEAWorkSpace\Frame_SSM\exercise-spring\Spring总结.assets\image-20211128221222633.png)]
使用方式:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-014G9WU2-1638109011028)(E:\IDEAWorkSpace\Frame_SSM\exercise-spring\Spring总结.assets\image-20211128221241735.png)]
(3)timeout:超时时间
(1)事务需要在一定时间内进行提交,如果不提交进行回滚
(2)默认值是 -1 ,设置时间以秒单位进行计算
(4)readOnly:是否只读
(1)读:查询操作,写:添加修改删除操作
(2)readOnly 默认值 false,表示可以查询,可以添加修改删除操作
(3)设置 readOnly 值是 true,设置成 true 之后,只能查询
(5)rollbackFor:回滚
(1)设置出现哪些异常进行事务回滚
(6) noRollbackFor:不回滚
(1)设置出现哪些异常不进行事务回滚
Spring的AOP即声明式事务管理默认是针对unchecked exception回滚。Spring的事务边界是在调用业务方法之前开始的,业务方法执行完毕之后来执行commit or rollback(Spring默认取决于是否抛出runtimeException)。
如果你在方法中有try{}catch(Exception e){}处理,那么try里面的代码块就脱离了事务的管理,若要事务生效需要在catch中throw new RuntimeException (“xxxxxx”);这一点也是面试中会问到的事务失效的场景
1、就是@Transactional注解保证的是每个方法处在一个事务,如果有try一定在catch中抛出运行时异常。
2、方法必须是public修饰符。否则注解不会生效,但是加了注解也没啥毛病,不会报错,只是没卵用而已。
3、this.本方法的调用,被调用方法上注解是不生效的,因为无法再次进行切面增强。
6: PROPAGATION_NEVER
不能在事务中运行。假设ServiceA.methodA的事务级别是PROPAGATION_REQUIRED, 而ServiceB.methodB的事务级别是PROPAGATION_NEVER ,那么ServiceB.methodB就要抛出异常了。
7: PROPAGATION_NESTED
理解Nested的关键是savepoint。他与PROPAGATION_REQUIRES_NEW的区别是,PROPAGATION_REQUIRES_NEW另起一个事务,将会与他的父事务相互独立,而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的。
而Nested事务的好处是他有一个savepoint。
isolation:事务的隔离级别
1. ISOLATION_DEFAULT: 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.
另外四个与JDBC的隔离级别相对应
ISOLATION_READ_UNCOMMITTED: 这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
ISOLATION_READ_COMMITTED: 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据
ISOLATION_REPEATABLE_READ: 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。 它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。 除了防止脏读,不可重复读外,还避免了幻像读。
什么是脏数据,脏读,不可重复读,幻觉读?
脏读: 指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据, 那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。
不可重复读: 指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据 可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
幻觉读: 指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么, 以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。
1.修改applicationContext.xml
<!--1.配置平台事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!--2.二配置事务注解 --> <tx:annotation-driven transaction-manager="transactionManager"/>
2.在业务逻辑类上面使用注解
@Transactional : 如果在类上声明,那么标记着该类中的所有方法都使用事务管理。也可以作用于方法上,那么这就表示只有具体的方法才会使用事务。
注解型事务的参数类型以及相关意思:
(1) : propagation:事务传播行为
(1)多事务方法直接进行调用,这个过程中事务 是如何进行管理的
使用方式:
(2) : ioslation:事务隔离级别
(1) 事务有特性成为隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题
(2) 有三个读问题:脏读、不可重复读、虚(幻)读
(1)脏读:一个未提交事务读取到另一个未提交事务的数据
(2)不可重复读:一个未提交事务读取到另一个事务修改的数据
(3)幻读:一个未提交事务读取到另一提交事务添加数据
使用事务的隔离级别可以解决脏读,幻读,不可重复读
使用方式:
(3)timeout:超时时间
(1)事务需要在一定时间内进行提交,如果不提交进行回滚
(2)默认值是 -1 ,设置时间以秒单位进行计算
(4)readOnly:是否只读
(1)读:查询操作,写:添加修改删除操作
(2)readOnly 默认值 false,表示可以查询,可以添加修改删除操作
(3)设置 readOnly 值是 true,设置成 true 之后,只能查询
(5)rollbackFor:回滚
(1)设置出现哪些异常进行事务回滚
(6) noRollbackFor:不回滚
(1)设置出现哪些异常不进行事务回滚
Spring的AOP即声明式事务管理默认是针对unchecked exception回滚。Spring的事务边界是在调用业务方法之前开始的,业务方法执行完毕之后来执行commit or rollback(Spring默认取决于是否抛出runtimeException)。
如果你在方法中有try{}catch(Exception e){}处理,那么try里面的代码块就脱离了事务的管理,若要事务生效需要在catch中throw new RuntimeException (“xxxxxx”);这一点也是面试中会问到的事务失效的场景
1、就是@Transactional注解保证的是每个方法处在一个事务,如果有try一定在catch中抛出运行时异常。
2、方法必须是public修饰符。否则注解不会生效,但是加了注解也没啥毛病,不会报错,只是没卵用而已。
3、this.本方法的调用,被调用方法上注解是不生效的,因为无法再次进行切面增强。
================================================
以上是小弟对Spring的一点理解和总结,喜欢的觉得有用的可以点个赞,关注走一波,有问题可以在评论区指出不足,给各位大佬抱拳了,共同进步,共同学习