文档章节

都知道的spring事务那点事(声明式,编程式)

1024菜bird
 1024菜bird
发布于 2016/11/07 08:25
字数 4691
阅读 656
收藏 66

为什么用Spring来进行事务控制?
   如果要手动进行控制事务的话,对于JDBC,service层需要Connection;对于Hibernate,serivice层需要Session。若一个项目要实现JDBC和Hibernate或其他的互换,我们要做Service层修改很多东西;而且对于Service层来说,他应该关心的不应该是这些,而是业务逻辑。因此,首先手动控制不能实现组件的替换,其次这些API也不应该出现在service层,但是Spring的IOC很好的解决了这样的问题。

.spring事物简介:

1.spring 事务分为编程式事务管理,声明式事务管理两大类,其中声明式事物有细分三种方式,两种常用的方式,一种是基于txaop名字空间的xml配置文件,另一种就是基于@Transactional注解。

2. 编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。

3. 声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。

 显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就可以获得完全的事务支持。和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。

4. Spring的事务管理器:

Spring事务策略是通过PlatformTransactionManager接口体现的,该接口是Spring事务策略的核心,是一个与任何事务策略分离的接口,随着底层不同事务策略的切换,应用必须采用不同的实现类。结合Spring的IoC容器,可以向该接口注入相关的平台特性。spring所有的事务管理策略类都继承自org.springframework.transaction.PlatformTransactionManager接口,这些事务管理器的的父接口都是PlatformTransactionManager.Spring的事务管理机制是一种典型的策略模式,PlatformTransactionManager代表事务管理接口(该接口定义了下面所说的三个方法),他并不知道底层如何管理事务,他只要求事务管理的实现类提供开始事务(getTransaction())、提交事务(commit())、回滚事务(rollback()),但具体如何实现则交给具体的实现类完成——不同的实现类代表不同的事务管理策略。

事务管理器实现

目标

org.springframework.jdbc.datasource.DataSourceTransactionManager

在JDBC DataSource中管理事务

(须注入数据源datasource Bean参数)

org.springframework.orm.hibernate.HibernateTransactionManager

管理Hibernate事务

(须注入SessionFactory Bean参数)

org.springframework.orm.jdo.JdoTransactionManager

管理JDO事务

org.springframework.transaction.jta.JtaTransactionManager

使用一个JTA管理事务,在一个事务跨越多个资源时必须使用

(无须注入参数)

org.springframework.orm.ojb.PersistenceBrokerTransactionManager

管理Apache的OJB事务

 1)、jdbc事务:每个Connection都带有一个事务,只是默认被设置为自动提交。一个连接可以有多个事务。对于JDBC,只有在同一个连接内,才有讨论是否提交的前提。
  2)、Hibernate事务:本质上也是使用JDBC来处理事务。但是不是直接操作,而是使用Session来操作事务。Session.getTranction();
  ####事务应该要在service层(也可以叫事务层)进行控制。

3)、JTA事务管理器无须注入参数,是因为全局事务的JTA资源由JAVA EE服务器提供,而Spring容器能自行从JAVA EE服务器中获取该事务资源,所以无须使用依赖注入来配置。

当使用JTA全局事务策略时,实际底层须应用服务器支持,而不同的应用服务器所提供的JTA全局事务可能存在细节上的差异,因此实际配置全局事务管理器是可能需要使用JtaTransactionManager的子类,如:OC4JtaTransactionManager(Oracle提供的应用服务器)、WebLogicJtaTransactionManager(Bea提供的WebLogic)、UowJtaTransactionManager(IBM提供的WebSphere)等

全局事务和局部事务。全局事务由应用服务器管理,需要底层服务器JTA支持(如WebLogic、JBoss等)。局部事务和底层采用的持久化技术有关:当采用JDBC持久化技术时,需要使用Connetion对象来操作事务;而采用Hibernate持久化技术时,需要使用Session对象来操作事务。
   全局事务可以跨多个事务性的资源(典型例子是关系数据库和消息队列);使用局部事务,应用服务器不需要参与事务管理,因此不能保证跨多个事务性资源的事务的正确性。当然,实际上大部分应用都使用单一事务性的资源。

5. spring事务特性,spring所有的事务管理策略类都继承自org.springframework.transaction.PlatformTransactionManager接口,

其中TransactionDefinition接口定义以下特性:

1)事务隔离级别

  隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:

·         TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED。

·         TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。

·         TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。

·         TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。

·         TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

·         TransactionDefinition.PROPAGATION_REQUIRED:业务方法需要在一个容器里运行。如果方法运行时,已经处在一个事务中,那么加入到这个事务,否则自己新建一个新的事务。 

·         TransactionDefinition.PROPAGATION_REQUIRES_NEW:不管是否存在事务,该方法总汇为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务挂起,新的事务被创建。 

·         TransactionDefinition.PROPAGATION_SUPPORTS:该方法在某个事务范围内被调用,则方法成为该事务的一部分。如果方法在该事务范围外被调用,该方法就在没有事务的环境下执行。 

·         TransactionDefinition.PROPAGATION_NOT_SUPPORTED:声明方法不需要事务。如果方法没有关联到一个事务,容器不会为他开启事务,如果方法在一个事务中被调用,该事务会被挂起,调用结束后,原先的事务会恢复执行。 

·         TransactionDefinition.PROPAGATION_NEVER:该方法绝对不能在事务范围内执行。如果在就抛例外。只有该方法没有关联到任何事务,才正常执行。 

·         TransactionDefinition.PROPAGATION_MANDATORY:该方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果在没有事务的环境下被调用,容器抛出例外。 

·         TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。 

·         事务超时

·               所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。

·           默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。

·         事务只读属性

·         @Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true),这样就做成一个只读事务,可以提高效率。 

·               只读事务用于客户代码只读但不修改数据的情形,只读事务用于特定情景下的优化,比如使用Hibernate的时候。

·         默认为读写事务。

·          “只读事务”并不是一个强制选项,它只是一个“暗示”,提示数据库驱动程序和数据库系统,这个事务并不包含更改数据的操作,那么JDBC驱动程序和数据库就有可能根据这种情况对该事务进行一些特定的优化,比方说不安排相应的数据库锁,以减轻事务对数据库的压力,毕竟事务也是要消耗数据库的资源的。 

·         但是你非要在“只读事务”里面修改数据,也并非不可以,只不过对于数据一致性的保护不像“读写事务”那样保险而已。 

·         因此,“只读事务”仅仅是一个性能优化的推荐配置而已,并非强制你要这样做不可

二、Spring编程式事务示例

·         步骤一、编写spring配置文件

·         下面实例使用DataSourceTransactionManager来管理JDBC事务。
查看Spring的配置信息:(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:p="http://www.springframework.org/schema/p"
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

·         <bean id="propertyConfig"
         class="org.springframework.beans.factory.config.

·           PropertyPlaceholderConfigurer">
       <property name="locations">

·                              <list>

·                                        <value>classpath:jdbc.properties</value>

·                              </list>

·                     </property>     </bean>

·         <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">

·                     <property name="driverClass" value="${jdbc.driverClassName}" />

·                     <property name="jdbcUrl" value="${jdbc.url}" />

·                     <property name="user" value="${jdbc.username}" />

·                     <property name="password" value="${jdbc.password}" />

·                     <property name="autoCommitOnClose" value="true"/>

·                     <property name="minPoolSize" value="10"/><!--连接池中保留的最小连接数。-->

·                     <property name="maxPoolSize" value="100" /><!--连接池中保留的最大连接数。Default: 15 -->

·                     <property name="maxIdleTime" value="1800" /><!--最大空闲时间,1800秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->

·                     <property name="acquireIncrement" value="5" /><!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 -->

·                     <property name="maxStatements" value="0" />

·                     <!--  maxStatementsPerConnection定义了连接池内单个连接所拥有的最大缓存statements数。Default: 0 -->

·                     <property name="maxStatementsPerConnection" value="0"/>

·                     <property name="initialPoolSize" value="10" />

·                     <property name="idleConnectionTestPeriod" value="3600" /><!--每60秒检查所有连接池中的空闲连接。Default: 0 -->

·                     <property name="acquireRetryAttempts" value="30" /><!--定义在从数据库获取新连接失败后重复尝试的次数。Default: 30 -->

·                     <property name="breakAfterAcquireFailure" value="false" />

·                     <property name="testConnectionOnCheckout" value="false" />

·            </bean>

·         <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
         <property name="dataSource">
             <ref bean="dataSource" />
         </property>
     </bean>

·         <!-- JDBC事务管理器 注意:事务管理器传的参数是数据源-->
     <bean id="transactionManager"
         class="org.springframework.jdbc.datasource.

·                DataSourceTransactionManager" scope="singleton">
         <property name="dataSource">
             <ref bean="dataSource" />
         </property>
     </bean>
 

·         <!-- 声明事务模板 -->
     <bean id="transactionTemplate"
         class="org.springframework.transaction.support.TransactionTemplate">
         <property name="transactionManager">
             <ref bean="transactionManager" />
         </property>
     </bean>

·         <bean id="bankDao" class="com.sunflower.dao.BankDaoImp">
         <property name="jdbcTemplate">
             <ref bean="jdbcTemplate" />
         </property>
         <property name="transactionTemplate">
             <ref bean="transactionTemplate" />
         </property>
     </bean>

·         上 面代码中配置了一个org.springframework.transaction.support.TransactionTemplate实例,要 在代码中添加事务,Spring为我们提供了一种方法就是使用TransactionTemplate类。我们要为 TransactionTemplate装配一个TransactionManager,

·         如果是要配置Hibernate事务,要进行如下配置:(配置一个sessionFactory):
<!-- Hibernate事务管理器  注意:此事务管理器参数是sessionFactory-->
     <bean id="transactionManager"
   class="org.springframework.orm.hibernate3.
   HibernateTransactionManager" scope="singleton">    
         <property name="sessionFactory">
             <ref bean="sessionFactory" />
         </property>
     </bean>

·         如果是要配置JTA事务,要进行如下配置(无须参数):
 <bean id="transactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager" scope="singleton" >
  </bean>

·         步骤二、使用TransactionTemplate进行事务管理:
package com.sunflower.dao;
 
 import java.sql.ResultSet;
 import java.sql.SQLException;
 
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowCallbackHandler;
 import org.springframework.transaction.TransactionStatus;
 import org.springframework.transaction.support.TransactionCallback;
 import org.springframework.transaction.support.TransactionTemplate;
 
 import com.sunflower.entity.People;
 
 
 public class BankDaoImp implements BankDao {
     private JdbcTemplate jdbcTemplate;

·           //注入声明式事物模板
     private TransactionTemplate transactionTemplate;
 
     public JdbcTemplate getJdbcTemplate() {
         return jdbcTemplate;
     }
 
     public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
         this.jdbcTemplate = jdbcTemplate;
     }
 
     public TransactionTemplate getTransactionTemplate() {
         return transactionTemplate;
     }
 
     public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
         this.transactionTemplate = transactionTemplate;
     }
 
     @Override
     public double getMoney(final People people) {
         double money = people.getMoney();
         // 开始事务,如果出现状况则回滚
        transactionTemplate.execute(new TransactionCallback<People>() {
             @Override
             public People doInTransaction(TransactionStatus ts) {
                 try {
                     final People people2 = new People();
                     // 使用JdbcTemplate进行持久化层操作
                     String sql = "select money from bank where name = ?";
                     Object[] params = new Object[] { people.getName() };
                     // 查询
                     jdbcTemplate.query(sql, params, new RowCallbackHandler() {
                         @Override
                         public void processRow(ResultSet rs)
                                 throws SQLException {
                            people2.setMoney(rs.getDouble("money"));
                            System.out.println(people.getName() + "用户还有"
                                     + rs.getDouble("money") + "元余款");
                            System.out.println(people.getName() + "要从账户中取出"
                                     + people.getMoney() + "元");
                             if (people2.getMoney() < people.getMoney()) {
                                 System.out.println("余额不足");
                                 people.setMoney(-1);
                                 return;
                             }
                         }
                     });
 
                     if (people.getMoney() < 0)
                         return null;
                     else {
                         sql = "update bank set money = ? where name = ?";
                         Object[] params2 = new Object[] {
                                 people2.getMoney() - people.getMoney(),
                                 people.getName() };
                         jdbcTemplate.update(sql, params2);
                         System.out.println("剩余余额:"
                                 + (people2.getMoney() - people.getMoney()));
                     }
                 }
                 catch (Exception e) {
                     ts.setRollbackOnly();
                 }
 
                 // 如果成功,事务被提交
                 return people;
             }
         });
 
         return people.getMoney();
     }
 }
调 用TransactionTemplate实例的execute()方法将执行包含在TransactionCallback实例里的代码。如果代码出现 异常,调用TransactionStatus对象的setRollbackOnly()将事务回滚。否则,如果doInTransaction()方法 正常返回,事务将被提交。

三、Spring声明式事务管理:

1、基于TransactionProxyFactoryBean的方式(很少使用) 

     需要为每个事务管理的类配置一个TransactionProxyFactoryBean进行管理。使用时还需要在类中注入该代理类。

2、基于AspectJ的方式(常使用)

 配置好之后,按照方法的名字进行管理,无需再类中添加任何东西。

可以配置多个切入点,以及异常处理切面

 3、基于注解的方式(经常使用)详细介绍下

  在spring的事务管理中,我们可以使用@Transactional这一annotation来对事务进行声明式的设定。具体而言,就是在类或者方法前添加@Transactional并传入属性参数以获取所需要的Transaction特性。Spring中的@Transactional有5个属性:Propagation、Isolation、Rollback Rules、Timeout和Read-Only,其中Propagation属性定义了Transaction的边界 — 是否使用Transaction、在Transaction已存在的情况下如何表现等。

在service类前加上@Transactional,声明这个service所有方法需要事务管理。每一个业务方法开始时都会打开一个事务,在单独使用不带任何参数的 @Transactional 注释时,传播模式要设置为 REQUIRED,只读标志设置为 false,事务隔离级别设置为 READ_COMMITTED,而且事务不会针对受控异常(checked exception)回滚。

@Transactional属性 

属性

类型

描述

value

String

可选的限定描述符,指定使用的事务管理器

propagation

enum: Propagation

可选的事务传播行为设置

isolation

enum: Isolation

可选的事务隔离级别设置

readOnly

boolean

读写或只读事务,默认读写

timeout

int (in seconds granularity)

事务超时时间设置

rollbackFor

Class对象数组,必须继承自Throwable

导致事务回滚的异常类数组

rollbackForClassName

类名数组,必须继承自Throwable

导致事务回滚的异常类名字数组

noRollbackFor

Class对象数组,必须继承自Throwable

不会导致事务回滚的异常类数组

noRollbackForClassName

类名数组,必须继承自Throwable

不会导致事务回滚的异常类名字数组

 用法

       @Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。

         虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。

        默认情况下,只有来自外部的方法调用才会被AOP代理捕获,也就是,类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解进行修饰。


Spring默认情况下会对运行期例外(RunTimeException)进行事务回滚。这个例外是unchecked 

如果遇到checked意外就不回滚。 

如何改变默认规则: 

1 让checked例外也回滚:在整个方法前加上 @Transactional(rollbackFor=Exception.class) 

2 让unchecked例外不回滚: @Transactional(notRollbackFor= RuntimeException.class) 

    <!-- 配置Jdbc模板  -->

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" p:dataSource-ref="dataSource"/>

<!-- 配置事务管理器 -->

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSource" />

<tx:annotation-driven  transaction-manager="transactionManager" proxy-target-class="true"/>       

transaction-manager 属性保存一个对在 Spring 配置文件中定义的事务管理器 bean 的引用。这段代码告诉 Spring 在应用事务拦截器时使用 @Transaction 注释。如果没有它,就会忽略 @Transactional 注释,导致代码不会使用任何事务。 

或者

四、结束语

至此spring事务相关的总结结束,本人周末闲来无事,花点时间总结下事务相关的内容,网上好多事务的说明,但是感觉总结的不全面,借鉴度娘上资源及自己的一点经验,希望对新人有点帮助,不过时间有点紧,难免会有总结不周全或不准确的地方,如果有更好的建议,希望码农们提出宝贵意见,后续完善。

 

 2个问题,讨论下,相信大家自己手动试验后会有正确的结果(一周后答案解释附上):

question1:

@Transactional(readOnly = true, propagation=Propagation.SUPPORTS) 
public long insertTrade(TradeData trade) throws Exception { 
   insertTrade();//这是一条插入sql语句,1.数据会不会插入数据库?2.直接抛出异常 ?

 System.out.println(4/0);

question2:

@Transactional(readOnly = true, propagation=Propagation.REQUIRED)   
public long insertTrade(TradeData trade) throws Exception {   
   insertTrade()//这是一条插入sql语句,1.数据会不会插入数据库?2.直接抛出异常 ?

 System.out.println(4/0);

© 著作权归作者所有

1024菜bird
粉丝 78
博文 141
码字总数 138266
作品 0
海淀
程序员
私信 提问
加载中

评论(1)

心有灵犀
心有灵犀
怒赞,👍👍👍
分析 Spring 的编程式事务管理及声明式事务管理(转)

开始之前 关于本教程 本教程将深入讲解 Spring 简单而强大的事务管理功能,包括编程式事务和声明式事务。通过对本教程的学习,您将能够理解 Spring 事务管理的本质,并灵活运用之。 先决条件...

君辰
2015/07/27
0
0
spring,mybatis事务管理配置与@Transactional注解使用

概述 事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性。 Spring Framework对事务管理提供了一致的抽象,其特点如下: 为不同的事务API提供一致的编程模型...

北京-卫斯理
2015/07/30
0
1
怎么使用Spring配置事务 ?

Spring同时支持编程式事务策略和声明式事务策略,大部分时候都采用声明式事务策略。 声明式事务管理的配置方式,通常有以下4种: (1) 使用TransactionProxyFactoryBean为目标Bean生成事务代理...

a'ゞ浪人گق
2018/08/09
0
0
Spring编程式和声明式事务

1.编程式事务 1.1 编程式和声明式事务的区别 Spring提供了对编程式事务和声明式事务的支持,编程式事务允许用户在代码中精确定义事务的边界,而声明式事务(基于AOP)有助于用户将操作与事务...

梨加橙
2018/06/19
0
0
Spring的四大优势,你是如何理解的?

1、方便解耦,简化开发 Spring是分层的 Java SE/EE 应用 full-stack轻量级开源框架,以IoC(Inverse Of Control:反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提...

骚年锦时
05/15
0
0

没有更多内容

加载失败,请刷新页面

加载更多

FPGA 设备 USB Platform Cable USB

lsusbFuture Technology Devices International, Ltd FT232H Single HS USB-UART/FIFO IC

MtrS
今天
4
0
lua web快速开发指南(6) - Cache、DB介绍

"数据库"与"缓存"的基本概念 数据库与缓存是服务端开发人员的必学知识点. 数据库 "数据库"是一种信息记录、存取的虚拟标记地点的集合统称. 比如现实生活中, 我们经常会用到文件柜、书桌等等数...

水果糖的小铺子
今天
6
0
Oracle分页查询语句的写法

Oracle分页查询语句的写法 Oracle分页查询语句使我们最常用的语句之一,下面就为您介绍的Oracle分页查询语句的用法,Oracle分页查询语句基本上可以按本文给出的格式来进行套用。   Oracle分...

康师傅
昨天
5
0
java并发图谱

1527
昨天
2
0
Mybatis之拦截器Interceptor

使用mybatis时用PageHelper进行分页,用到了PageInterceptor,借此了解下mybatis的interceptor。Mybatis的版本是3.4.6,MybatisHelper的版本是5.1.3。 1、PageInterceptor 先上一段代码,如下...

克虏伯
昨天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部