Spring之Spring的事务控制
一、Spring中的事务
Spring 并不直接管理事务,而是提供了多种事务管理器,将事务管理的职责委托给JDBC、Hibernate等持久化机制所提供的相关平台框架的事务来实现。
Spring框架为提供了一组事务控制的接口,该接口是在spring-tx-*.jar 中。
PlatformTransactionManager
PlatformTransactionManager
接口是 spring的事务管理器,提供了操作事务的方法
public interface PlatformTransactionManager extends TransactionManager {
//获取事务状态信息
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
//提交事务
void commit(TransactionStatus var1) throws TransactionException;
//回滚事务
void rollback(TransactionStatus var1) throws TransactionException;
}
Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,具体的实现就是各个平台自己的事情。
JDBC 事务管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
Hibernate久化事务管理器
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
JPA持久化事务管理器
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
TransactionDefinition
TransactionDefinition接口是事务的信息定义对象
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = 1;
int ISOLATION_READ_COMMITTED = 2;
int ISOLATION_REPEATABLE_READ = 4;
int ISOLATION_SERIALIZABLE = 8;
int TIMEOUT_DEFAULT = -1;
//获取事务传播行为
default int getPropagationBehavior() {
return 0;
}
//获取事务隔离级别
default int getIsolationLevel() {
return -1;
}
//获取事务超时时间,默认值是-1,没有超时限制。如果有,以秒为单位进行设置。
default int getTimeout() {
return -1;
}
//获取事务是否只读,查询时设置为只读。
default boolean isReadOnly() {
return false;
}
//获取事务对象名称
@Nullable
default String getName() {
return null;
}
static TransactionDefinition withDefaults() {
return StaticTransactionDefinition.INSTANCE;
}
}
TransactionStatus
TransactionStatus接口是提供事务具体的运行状态信息。
public class DefaultTransactionStatus extends AbstractTransactionStatus {
@Nullable
private final Object transaction;
private final boolean newTransaction;
private final boolean newSynchronization;
private final boolean readOnly;
private final boolean debug;
@Nullable
private final Object suspendedResources;
//事务的默认状态
public DefaultTransactionStatus(@Nullable Object transaction, boolean newTransaction, boolean newSynchronization, boolean readOnly, boolean debug, @Nullable Object suspendedResources) {
this.transaction = transaction;
this.newTransaction = newTransaction;
this.newSynchronization = newSynchronization;
this.readOnly = readOnly;
this.debug = debug;
this.suspendedResources = suspendedResources;
}
//获取事务
public Object getTransaction() {
Assert.state(this.transaction != null, "No transaction active");
return this.transaction;
}
//事务是否存在
public boolean hasTransaction() {
return this.transaction != null;
}
//获取的事务是否是新的事务
public boolean isNewTransaction() {
return this.hasTransaction() && this.newTransaction;
}
public boolean isNewSynchronization() {
return this.newSynchronization;
}
//是否是只读事务
public boolean isReadOnly() {
return this.readOnly;
}
public boolean isDebug() {
return this.debug;
}
@Nullable
public Object getSuspendedResources() {
return this.suspendedResources;
}
//获取事务是否回滚
public boolean isGlobalRollbackOnly() {
return this.transaction instanceof SmartTransactionObject && ((SmartTransactionObject)this.transaction).isRollbackOnly();
}
//获取事务存储点管理对象
protected SavepointManager getSavepointManager() {
Object transaction = this.transaction;
if (!(transaction instanceof SavepointManager)) {
throw new NestedTransactionNotSupportedException("Transaction object [" + this.transaction + "] does not support savepoints");
} else {
return (SavepointManager)transaction;
}
}
//是否是事务存储点管理对象
public boolean isTransactionSavepointManager() {
return this.transaction instanceof SavepointManager;
}
//刷新事务
public void flush() {
if (this.transaction instanceof SmartTransactionObject) {
((SmartTransactionObject)this.transaction).flush();
}
}
}
事务的隔离级别
级别 | 名字 | 隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|---|
1 | 读未提交 | read uncommitted | 是 | 是 | 是 |
2 | 读已提交 | read committed | 否 | 是 | 是 |
3 | 可重复读 | repeatable read | 否 | 否 | 是 |
4 | 串行化 | serializable | 否 | 否 | 否 |
事务的传播行为
REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
NEVER:以非事务方式运行,如果当前存在事务,抛出异常
NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作。
二、基于 XML 的声明式事务控制
1.添加依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
</dependencies>
2.配置spring.xml
声明式事务控制配置步骤
1、配置事务管理器
2、配置事务的通知
导入事务的名称空间和约束 xmlns:tx="http://www.springframework.org/schema/tx"
3、配置AOP中的通用切入点表达式
导入aop的名称空间和约束 xmlns:aop="http://www.springframework.org/schema/aop"
4、建立事务通知和切入点表达式的对应关系
5、配置事务的属性
isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。
<?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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置accountService-->
<bean id="accountService" class="cn.ybzy.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置accountDao-->
<bean id="accountDao" class="cn.ybzy.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/demo"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<!-- 1.配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2.配置事务通知-->
<!-- transaction-manager属性表示这个事务通知是哪个事务管理器管理的 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 5.配置事务的属性-->
<tx:attributes>
<!-- 一般来说增删改方法 propagation=Required,对于查询方法使用 read-only="true" -->
<!-- name是必须的,表示与事务属性关联的方法名,对切入点进行细化
通配符(*)可以用来指定一批关联到相同的事务属性的方法,如:'save*'、'update*'等 -->
<!--对以add update delete开头的所有方法进行事务处理-->
<tx:method name="add*" propagation="REQUIRED" />
<!-- 匹配以 update 开头的所有方法均加入事务 -->
<tx:method name="update*" propagation="REQUIRED" />
<!-- 匹配以 delete 开头的所有方法均加入事务 -->
<tx:method name="delete*" propagation="REQUIRED" />
<!-- 匹配以 find 开头的所有方法均加入事务,且是只读,不能修改数据库数据 -->
<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
<!-- 配置aop-->
<aop:config>
<!-- 3.配置切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* cn.ybzy.service.impl.*.*(..))"></aop:pointcut>
<!--4.建立切入点表达式和事务通知的对应关系 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
</beans>
3.编写代码
Account实体对象
public class Account implements Serializable {
private Integer id;
private String name;
private BigDecimal money;
//getter and setter
}
IAccountDao 接口与AccountDaoImpl实现类
public interface IAccountDao {
/**
* 根据名称查询账户
* @param accountName
* @return
*/
Account findAccountByName(String accountName);
/**
* 更新账户
* @param account
*/
void updateAccount(Account account);
}
public class AccountDaoImpl implements IAccountDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public Account findAccountByName(String accountName) {
List<Account> accounts = jdbcTemplate.query("select * from account where name = ?",new BeanPropertyRowMapper<Account>(Account.class),accountName);
if(accounts.isEmpty()){
return null;
}
return accounts.get(0);
}
@Override
public void updateAccount(Account account) {
jdbcTemplate.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
}
IAccountService 接口与AccountServiceImpl实现类
public interface IAccountService {
/**
* 转账
* @param sourceName 转成账户名称
* @param targetName 转入账户名称
* @param money 转账金额
*/
void transfer(String sourceName, String targetName, BigDecimal money);
}
public class AccountServiceImpl implements IAccountService{
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(String sourceName, String targetName, BigDecimal money) {
//1根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//3转出账户减钱
source.setMoney(source.getMoney().subtract(money));
//4转入账户加钱
target.setMoney(target.getMoney().add(money));
//5.更新转出账户
accountDao.updateAccount(source);
// int i=1/0;
//6更新转入账户
accountDao.updateAccount(target);
}
}
4.执行测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testTransfer(){
as.transfer("A","B", BigDecimal.valueOf(100));
}
}
三、基于注解的声明式事务控制
1.添加依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
</dependencies>
2.配置spring.xml
注解的声明式事务控制配置步骤
1、配置事务管理器
2、开启spring对注解事务的支持
3、在需要事务支持的地方使用@Transactional注解
<?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"
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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置扫描的包-->
<context:component-scan base-package="cn.ybzy"></context:component-scan>
<!-- 配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/demo"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启spring对注解事务的支持-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
3.编写代码
Account实体对象
public class Account implements Serializable {
private Integer id;
private String name;
private BigDecimal money;
//getter and setter
}
IAccountDao 接口与AccountDaoImpl实现类
public interface IAccountDao {
/**
* 根据名称查询账户
* @param accountName
* @return
*/
Account findAccountByName(String accountName);
/**
* 更新账户
* @param account
*/
void updateAccount(Account account);
}
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public Account findAccountByName(String accountName) {
List<Account> accounts = jdbcTemplate.query("select * from account where name = ?",new BeanPropertyRowMapper<Account>(Account.class),accountName);
if(accounts.isEmpty()){
return null;
}
return accounts.get(0);
}
@Override
public void updateAccount(Account account) {
jdbcTemplate.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
}
IAccountService 接口与AccountServiceImpl实现类
public interface IAccountService {
/**
* 转账
* @param sourceName 转成账户名称
* @param targetName 转入账户名称
* @param money 转账金额
*/
void transfer(String sourceName, String targetName, BigDecimal money);
}
@Service(value = "accountService")
//只读型事务的配置
@Transactional(propagation= Propagation.SUPPORTS,readOnly=true)
// 可读可写型事务的配置
// @Transactional(propagation=Propagation.REQUIRED)
public class AccountServiceImpl implements IAccountService{
@Autowired
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
//读写型事务配置
@Transactional(propagation= Propagation.REQUIRED,readOnly=false)
@Override
public void transfer(String sourceName, String targetName, BigDecimal money) {
//1根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//3转出账户减钱
source.setMoney(source.getMoney().subtract(money));
//4转入账户加钱
target.setMoney(target.getMoney().add(money));
//5.更新转出账户
accountDao.updateAccount(source);
// int i=1/0;
//6更新转入账户
accountDao.updateAccount(target);
}
}
注意:默认情况下aop只捕获 Runtimeexception 的异常,但可以通过配置来捕获特定的异常并回 滚
4.执行测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testTransfer(){
as.transfer("A","B", BigDecimal.valueOf(100));
}
}
四、基于纯注解的声明式事务控制
1.添加依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
</dependencies>
2.编写代码
Account实体对象
public class Account implements Serializable {
private Integer id;
private String name;
private BigDecimal money;
//getter and setter
}
IAccountDao 接口与AccountDaoImpl实现类
public interface IAccountDao {
/**
* 根据名称查询账户
* @param accountName
* @return
*/
Account findAccountByName(String accountName);
/**
* 更新账户
* @param account
*/
void updateAccount(Account account);
}
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public Account findAccountByName(String accountName) {
List<Account> accounts = jdbcTemplate.query("select * from account where name = ?",new BeanPropertyRowMapper<Account>(Account.class),accountName);
if(accounts.isEmpty()){
return null;
}
return accounts.get(0);
}
@Override
public void updateAccount(Account account) {
jdbcTemplate.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
}
IAccountService 接口与AccountServiceImpl实现类
public interface IAccountService {
/**
* 转账
* @param sourceName 转成账户名称
* @param targetName 转入账户名称
* @param money 转账金额
*/
void transfer(String sourceName, String targetName, BigDecimal money);
}
@Service(value = "accountService")
//只读型事务的配置
@Transactional(propagation= Propagation.SUPPORTS,readOnly=true)
public class AccountServiceImpl implements IAccountService{
@Autowired
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
//读写型事务配置
@Transactional(propagation= Propagation.REQUIRED,readOnly=false)
@Override
public void transfer(String sourceName, String targetName, BigDecimal money) {
//1根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//3转出账户减钱
source.setMoney(source.getMoney().subtract(money));
//4转入账户加钱
target.setMoney(target.getMoney().add(money));
//5.更新转出账户
accountDao.updateAccount(source);
// int i=1/0;
//6更新转入账户
accountDao.updateAccount(target);
}
}
3.数据库相关配置
jdbcConfig.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/demo
jdbc.username=root
jdbc.password=123456
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;
/**
* 创建JdbcTemplate
* @param dataSource
* @return
*/
@Bean(name="jdbcTemplate")
public JdbcTemplate createJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
/**
* 创建数据源对象
* @return
*/
@Bean(name="dataSource")
public DataSource createDataSource(){
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
}
4.配置事务管理器对象
public class TransactionConfig {
/**
* 用于创建事务管理器对象
* @param dataSource
* @return
*/
@Bean(name="transactionManager")
public PlatformTransactionManager createTransactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
5.spring配置类,替代bean.xml
@Configuration
@ComponentScan("cn.ybzy")
@Import({JdbcConfig.class,TransactionConfig.class})
@PropertySource("jdbcConfig.properties")
@EnableTransactionManagement
public class SpringConfiguration {
}
6.执行测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= SpringConfiguration.class)
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testTransfer(){
as.transfer("A","B",BigDecimal.valueOf(100));
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/137128.html