Mybatis的多数据源操作

导读:本篇文章讲解 Mybatis的多数据源操作,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

Mybatis的多数据源操作,由于项目可能涉及到多个库的操作,那么就要设置多个数据,从网上查找可以发现有很多种方法,但是我觉得通过继承的方式实现数据源的切换。
1.实现原理:

扩展Spring的AbstractRoutingDataSource抽象类(该类充当了DataSource的路由中介, 能有在运行时, 根据某种key值来动态切换到真正的DataSource上。)

从AbstractRoutingDataSource的源码中:
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean

2.由此可见我们通过继承AbstractDataSource ,而AbstractDataSource不就是javax.sql.DataSource的子类,可以分析下它的getConnection方法:

public Connection getConnection() throws SQLException {  
    return determineTargetDataSource().getConnection();  
 }  
   
 public Connection getConnection(String username, String password) throws SQLException {  
      return determineTargetDataSource().getConnection(username, password);  
 }

3.获取连接的方法中,重点是determineTargetDataSource()方法,看源码:

/**
     * Retrieve the current target DataSource. Determines the
     * {@link #determineCurrentLookupKey() current lookup key}, performs
     * a lookup in the {@link #setTargetDataSources targetDataSources} map,
     * falls back to the specified
     * {@link #setDefaultTargetDataSource default target DataSource} if necessary.
     * @see #determineCurrentLookupKey()
     */
    protected DataSource determineTargetDataSource() {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
        Object lookupKey = determineCurrentLookupKey();
        DataSource dataSource = this.resolvedDataSources.get(lookupKey);
        if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
            dataSource = this.resolvedDefaultDataSource;
        }
        if (dataSource == null) {
            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
        }
        return dataSource;
    }

上面这段源码的重点在于determineCurrentLookupKey()方法,这是AbstractRoutingDataSource类中的一个抽象方法,而它的返回值是你所要用的数据源dataSource的key值,有了这个key值,在通过resolvedDataSource(这是个map,由配置文件中设置好后存入的)就从中取出对应的DataSource,如果找不到,就用配置默认的数据源。

看完源码,应该有点启发了吧,没错!你要扩展AbstractRoutingDataSource类,并重写其中的determineCurrentLookupKey()方法,来实现数据源的切换

实操:

  1. 搭建一个SpringMvc+String +Mybatis 的Maven项目

pom.xml文件引入AOP相关依赖

1 <project xmlns="http://maven.apache.org/POM/4.0.0"
  2     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  4     <modelVersion>4.0.0</modelVersion>
  5     <groupId>com.test</groupId>
  6     <artifactId>test-spring-mybatis</artifactId>
  7     <packaging>war</packaging>
  8     <version>1.0.0-SNAPSHOT</version>
  9     <url>http://maven.apache.org</url>
 10 
 11     <!-- 定义maven变量 -->
 12     <properties>
 13         <!-- spring -->
 14         <spring.version>5.1.4.RELEASE</spring.version>
 15 
 16         <!-- Mybatis -->
 17         <mybatis.version>3.5.0</mybatis.version>
 18         <!-- Mybatis 整合 Spring -->
 19         <mybatis-spring.version>2.0.0</mybatis-spring.version>
 20 
 21         <!-- mysql -->
 22         <mysql.version>8.0.13</mysql.version>
 23 
 24         <!-- c3p0 连接池 -->
 25         <c3p0.version>0.9.5.4</c3p0.version>
 26 
 27         <!-- logback -->
 28         <slf4j-api.version>1.7.5</slf4j-api.version>
 29         <logback.version>0.9.30</logback.version>
 30 
 31         <!-- Servlet -->
 32         <servlet.version>3.0.1</servlet.version>
 33         <jsp-api.version>2.2</jsp-api.version>
 34 
 35         <!-- jstl -->
 36         <jstl.version>1.2</jstl.version>
 37         <standard.version>1.1.2</standard.version>
 38 
 39         <!-- test junit -->
 40         <junit.version>3.8.1</junit.version>
 41 
 42         <!-- jdk -->
 43         <jdk.version>1.8</jdk.version>
 44         <maven.compiler.plugin.version>2.3.2</maven.compiler.plugin.version>
 45     </properties>
 46 
 47 
 48     <dependencies>
 49 
 50         <!-- Spring IOC 核心容器 -->
 51         <dependency>
 52             <groupId>org.springframework</groupId>
 53             <artifactId>spring-core</artifactId>
 54             <version>${spring.version}</version>
 55         </dependency>
 56 
 57         <dependency>
 58             <groupId>org.springframework</groupId>
 59             <artifactId>spring-beans</artifactId>
 60             <version>${spring.version}</version>
 61         </dependency>
 62 
 63         <dependency>
 64             <groupId>org.springframework</groupId>
 65             <artifactId>spring-context</artifactId>
 66             <version>${spring.version}</version>
 67         </dependency>
 68 
 69         <dependency>
 70             <groupId>org.springframework</groupId>
 71             <artifactId>spring-expression</artifactId>
 72             <version>${spring.version}</version>
 73         </dependency>
 74 
 75         <!-- Spring AOP 切面 模块 -->
 76         <dependency>
 77             <groupId>org.springframework</groupId>
 78             <artifactId>spring-aop</artifactId>
 79             <version>${spring.version}</version>
 80         </dependency>
 81 
 82         <dependency>
 83             <groupId>org.aspectj</groupId>
 84             <artifactId>aspectjrt</artifactId>
 85             <version>1.9.2</version>
 86         </dependency>
 87         
 88         <dependency>
 89             <groupId>org.aspectj</groupId>
 90             <artifactId>aspectjweaver</artifactId>
 91             <version>1.9.2</version>
 92         </dependency>
 93 
 94         <!-- Spring WEB MVC 模块 -->
 95         <dependency>
 96             <groupId>org.springframework</groupId>
 97             <artifactId>spring-web</artifactId>
 98             <version>${spring.version}</version>
 99         </dependency>
100 
101         <dependency>
102             <groupId>org.springframework</groupId>
103             <artifactId>spring-webmvc</artifactId>
104             <version>${spring.version}</version>
105         </dependency>
106 
107         <!-- Spring 事物 模块 -->
108         <dependency>
109             <groupId>org.springframework</groupId>
110             <artifactId>spring-tx</artifactId>
111             <version>${spring.version}</version>
112         </dependency>
113 
114         <!-- Spring ORM 对象关系映射 模块 -->
115         <dependency>
116             <groupId>org.springframework</groupId>
117             <artifactId>spring-orm</artifactId>
118             <version>${spring.version}</version>
119         </dependency>
120 
121         <!-- Spring JDBC 模块 -->
122         <dependency>
123             <groupId>org.springframework</groupId>
124             <artifactId>spring-jdbc</artifactId>
125             <version>${spring.version}</version>
126         </dependency>
127 
128         <!-- Mybatis -->
129         <dependency>
130             <groupId>org.mybatis</groupId>
131             <artifactId>mybatis</artifactId>
132             <version>${mybatis.version}</version>
133         </dependency>
134 
135         <!-- Mybatis 整合 Spring -->
136         <dependency>
137             <groupId>org.mybatis</groupId>
138             <artifactId>mybatis-spring</artifactId>
139             <version>${mybatis-spring.version}</version>
140         </dependency>
141 
142         <!-- mysql -->
143         <dependency>
144             <groupId>mysql</groupId>
145             <artifactId>mysql-connector-java</artifactId>
146             <version>${mysql.version}</version>
147         </dependency>
148 
149         <!-- c3p0 连接池 -->
150         <!-- https://mvnrepository.com/artifact/c3p0/c3p0 -->
151         <dependency>
152             <groupId>com.mchange</groupId>
153             <artifactId>c3p0</artifactId>
154             <version>${c3p0.version}</version>
155         </dependency>
156 
157 
158         <!-- logback -->
159         <dependency>
160             <groupId>org.slf4j</groupId>
161             <artifactId>slf4j-api</artifactId>
162             <version>${slf4j-api.version}</version>
163             <type>jar</type>
164             <scope>compile</scope>
165         </dependency>
166 
167         <dependency>
168             <groupId>ch.qos.logback</groupId>
169             <artifactId>logback-core</artifactId>
170             <version>${logback.version}</version>
171             <type>jar</type>
172         </dependency>
173 
174         <dependency>
175             <groupId>ch.qos.logback</groupId>
176             <artifactId>logback-classic</artifactId>
177             <version>${logback.version}</version>
178             <type>jar</type>
179         </dependency>
180 
181         <dependency>
182             <groupId>ch.qos.logback</groupId>
183             <artifactId>logback-access</artifactId>
184             <version>${logback.version}</version>
185         </dependency>
186 
187 
188         <!-- Servlet -->
189         <dependency>
190             <groupId>javax.servlet</groupId>
191             <artifactId>javax.servlet-api</artifactId>
192             <version>${servlet.version}</version>
193             <scope>provided</scope>
194         </dependency>
195         <dependency>
196             <groupId>javax.servlet.jsp</groupId>
197             <artifactId>jsp-api</artifactId>
198             <version>${jsp-api.version}</version>
199             <scope>provided</scope>
200         </dependency>
201 
202         <!-- jstl -->
203         <dependency>
204             <groupId>javax.servlet</groupId>
205             <artifactId>jstl</artifactId>
206             <version>${jstl.version}</version>
207         </dependency>
208 
209         <dependency>
210             <groupId>taglibs</groupId>
211             <artifactId>standard</artifactId>
212             <version>${standard.version}</version>
213         </dependency>
214 
215         <!-- test -->
216         <dependency>
217             <groupId>junit</groupId>
218             <artifactId>junit</artifactId>
219             <version>${junit.version}</version>
220             <scope>test</scope>
221         </dependency>
222 
223     </dependencies>
224 
225     <build>
226         <plugins>
227             <!-- define the project compile level -->
228             <plugin>
229                 <groupId>org.apache.maven.plugins</groupId>
230                 <artifactId>maven-compiler-plugin</artifactId>
231                 <version>${maven.compiler.plugin.version}</version>
232                 <configuration>
233                     <source>${jdk.version}</source>
234                     <target>${jdk.version}</target>
235                 </configuration>
236             </plugin>
237         </plugins>
238         <finalName>test_spring_mybatis</finalName>
239     </build>
240 </project>

2.创建一个类继承AbstractRoutingDataSource 类,DynamicDataSource.java

package net.chenlin.dp.common.mysource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * 动态数据源(依赖于spring)
 */
public class DynamicDataSource  extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceHolder.getDataSource();
    }
}

3.封装一个的对数据源进行操作的类,分别设置数据源和获取数据源和清除数据源,DataSourceHolder.java

package net.chenlin.dp.common.mysource;

public class DataSourceHolder {

    // 线程本地环境
    private static final ThreadLocal<String> dataSources = new ThreadLocal<String>();

    // 设置数据源
    public static void setDataSource(String customerType) {
        dataSources.set(customerType);
    }
    // 获取数据源
    public static String getDataSource() {
        return (String) dataSources.get();
    }

    // 清除数据源
    public static void clearDataSource() {
        dataSources.remove();
    }
}

4.我们可以通过Spring Aop设置,把配置的数据源设置成标签,在Service层需要切换数据源方法上添加,注解标签,调用相对应的数据源策略。TargetDataSource

package net.chenlin.dp.common.mysource;

import java.lang.annotation.*;

/**
 * 用于添加注解实现不同数据源的注解
 */
@Target({ElementType.METHOD,ElementType.TYPE})//作用在方法上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {

    String name() default TargetDataSource.MASTER;

    public static String MASTER="dataSource1";

    public static String SLAVE="dataSource2";
}

5.编辑切面的Bean,DataSourceExchange

/**
 * 编辑切面的Bean
 */
public class DataSourceExchange implements MethodBeforeAdvice, AfterReturningAdvice {
    /**
     * 运行完之后运行
     * @param o
     * @param method
     * @param objects
     * @param o1
     * @throws Throwable
     */
    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        DataSourceHolder.clearDataSource();
    }

    /**
     *
     * 运行方法前执行
     * @param method
     * @param objects
     * @param o
     * @throws Throwable
     */
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        // 这里TargetDataSource是自定义的注解
        if (method.isAnnotationPresent(TargetDataSource.class)){
            TargetDataSource annotation = method.getAnnotation(TargetDataSource.class);
            DataSourceHolder.setDataSource(annotation.name());
        }else{
            if (o.getClass().isAnnotationPresent(TargetDataSource.class)){
                TargetDataSource annotation = o.getClass().getAnnotation(TargetDataSource.class);
                DataSourceHolder.setDataSource(annotation.name());
            }
        }
    }
}

6.配置文件

<?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:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-4.2.xsd        
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-4.2.xsd
        http://www.springframework.org/schema/tx 
     	http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
		http://www.springframework.org/schema/aop
		http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

	<!-- 引入配置文件 -->
	<context:property-placeholder location="classpath:application.properties" />

	<!-- 数据源 配置 -->
	<bean id="dataSource1" class="com.alibaba.druid.pool.DruidDataSource"
		init-method="init" destroy-method="close">
		<!-- 基本属性 url、user、password -->
		<property name="url" value="${jdbc.url}" />
		<property name="username" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />

		<!-- 配置初始化大小、最小、最大 -->
		<property name="initialSize" value="1" />
		<property name="minIdle" value="1" />
		<property name="maxActive" value="20" />

		<!-- 配置获取连接等待超时的时间 -->
		<property name="maxWait" value="60000" />

		<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
		<property name="timeBetweenEvictionRunsMillis" value="60000" />

		<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
		<property name="minEvictableIdleTimeMillis" value="300000" />

		<property name="validationQuery" value="SELECT '1' from dual" />
		<property name="testWhileIdle" value="true" />
		<property name="testOnBorrow" value="false" />
		<property name="testOnReturn" value="false" />

		<!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
		<property name="poolPreparedStatements" value="false" />
		<property name="maxPoolPreparedStatementPerConnectionSize"
			value="20" />

		<!-- 配置监控统计拦截的filters -->
		<property name="filters" value="stat" />
	</bean>

	<bean id="dataSource2" class="com.alibaba.druid.pool.DruidDataSource"
		  init-method="init" destroy-method="close">
		<!-- 基本属性 url、user、password -->
		<property name="url" value="${jdbc.url.xql}" />
		<property name="username" value="${jdbc.username.xql}" />
		<property name="password" value="${jdbc.password.xql}" />

		<!-- 配置初始化大小、最小、最大 -->
		<property name="initialSize" value="1" />
		<property name="minIdle" value="1" />
		<property name="maxActive" value="20" />

		<!-- 配置获取连接等待超时的时间 -->
		<property name="maxWait" value="60000" />

		<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
		<property name="timeBetweenEvictionRunsMillis" value="60000" />

		<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
		<property name="minEvictableIdleTimeMillis" value="300000" />

		<property name="validationQuery" value="SELECT '1' from dual" />
		<property name="testWhileIdle" value="true" />
		<property name="testOnBorrow" value="false" />
		<property name="testOnReturn" value="false" />

		<!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
		<property name="poolPreparedStatements" value="false" />
		<property name="maxPoolPreparedStatementPerConnectionSize"
				  value="20" />

		<!-- 配置监控统计拦截的filters -->
		<property name="filters" value="stat" />
	</bean>


	<!-- 数据源:Spring用来控制业务逻辑。数据源、事务控制、aop -->
	<bean id="dataSource" class="net.chenlin.dp.common.mysource.DynamicDataSource">
		<property name="targetDataSources">
			<map key-type="java.lang.String">
				<entry key="dataSource1" value-ref="dataSource1"></entry>
				<entry key="dataSource2" value-ref="dataSource2"></entry>
			</map>
		</property>
		<!-- 默认目标数据源为你主库数据源 -->
		<property name="defaultTargetDataSource" ref="dataSource1"/>
	</bean>



	<!-- mybatis文件配置,扫描所有mapper文件 -->
	<bean id="sqlSessionFactory"
		  class="net.chenlin.dp.orm.mybatis.PackagesSqlSessionFactoryBean"
		  p:dataSource-ref="dataSource" p:typeAliasesPackage="net.chenlin.dp.**.entity"
		  p:configLocation="classpath:mybatis-config.xml"
		  p:mapperLocations="classpath:net/chenlin/dp/common/*/*.xml" />

	<!-- spring与mybatis整合配置,扫描所有dao -->
	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"
		p:basePackage="net.chenlin.dp.**.dao" p:sqlSessionFactoryBeanName="sqlSessionFactory" />

	<!-- 对dataSource 数据源进行事务管理 -->
	<bean id="transactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
		p:dataSource-ref="dataSource" />

	<!-- 配置切面的Bean -->
	<bean id="dataSourceExchange" class="net.chenlin.dp.common.mysource.DataSourceExchange"/>



	<!-- 事务管理 通知 -->
	<tx:advice id="txAdvice" transaction-manager="transactionManager">
		<tx:attributes>
			<!-- 对save,update,remove 开头的方法进行事务管理,只要有异常就回滚 -->
			<tx:method name="save*" propagation="REQUIRED"
				rollback-for="java.lang.Throwable" />
			<tx:method name="push*" propagation="REQUIRED"
					   rollback-for="java.lang.Throwable" />
			<tx:method name="upgoods*" propagation="REQUIRED"
					   rollback-for="java.lang.Throwable" />
			<tx:method name="remove*" propagation="REQUIRED"
				rollback-for="java.lang.Throwable" />
			<tx:method name="update*" propagation="REQUIRED"
				rollback-for="java.lang.Throwable" />
			<tx:method name="batch*" propagation="REQUIRED"
				rollback-for="java.lang.Throwable" />
			<tx:method name="clear*" propagation="REQUIRED"
				rollback-for="java.lang.Throwable" />
			<!-- select,count开头的方法,开启只读,提高数据库访问性能 -->
			<tx:method name="get*" read-only="true" />
			<tx:method name="list*" read-only="true" />
			<tx:method name="count*" read-only="true" />
			<!-- 对其他方法 使用默认的事务管理 -->
			<tx:method name="*" />
		</tx:attributes>
	</tx:advice>

	<!-- 事务 aop 配置 -->
	<aop:config>
		<aop:pointcut id="serviceMethods"
			expression="execution(* net.chenlin.dp.*.*.impl..*.*(..))" /> 切入点表达式
		<aop:advisor advice-ref="dataSourceExchange"  pointcut-ref="serviceMethods" order="1"/>
		<aop:advisor advice-ref="txAdvice"  pointcut-ref="serviceMethods"  order="2"/>
	</aop:config>

	<!-- 配置使Spring采用CGLIB代理 -->
	<aop:aspectj-autoproxy proxy-target-class="true" />

	<!-- 启用对事务注解的支持 -->
	<tx:annotation-driven transaction-manager="transactionManager" order="3" />

</beans>

需要注意的是此次扫描mybatis文件配置,扫描所有mapper文件不是spring的SqlSessionFactoryBean类而是我们自己继承SqlSessionFactoryBean写的类实现指定包的扫描

/**
 * mybatis支持扫描多个包路径
 *
 *
 * @email
 *
 * @date 2017年8月8日 上午11:28:59
 */
public class PackagesSqlSessionFactoryBean extends SqlSessionFactoryBean {

	static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";

	private static Logger logger = LoggerFactory.getLogger(PackagesSqlSessionFactoryBean.class);

	@Override
	public void setTypeAliasesPackage(String typeAliasesPackage) {
		ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver();
		MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver);
		typeAliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
				+ ClassUtils.convertClassNameToResourcePath(typeAliasesPackage) + "/" + DEFAULT_RESOURCE_PATTERN;

		try {
			List<String> result = new ArrayList<String>();
			Resource[] resources = resolver.getResources(typeAliasesPackage);
			if (resources != null && resources.length > 0) {
				MetadataReader metadataReader = null;
				for (Resource resource : resources) {
					if (resource.isReadable()) {
						metadataReader = metadataReaderFactory.getMetadataReader(resource);
						try {
							result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage()
									.getName());
						} catch (ClassNotFoundException e) {
							e.printStackTrace();
						}
					}
				}
			}
			if (result.size() > 0) {
				super.setTypeAliasesPackage(StringUtils.join(result.toArray(), ","));
			} else {
				logger.warn("参数typeAliasesPackage:" + typeAliasesPackage + ",未找到任何包");
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}

在这里插入图片描述

注意:特别要注意自己的切入点表达式有没有写错,这个很容易导致数据源切换失败。还有Spring中的事务是通过aop来实现的,当我们自己写aop拦截的时候,会遇到跟spring的事务aop执行的先后顺序问题,比如说动态切换数据源的问题,如果事务在前,数据源切换在后,会导致数据源切换失效,所以就用到了Order(排序)这个关键字

<aop:advisor advice-ref="dataSourceExchange"  pointcut-ref="serviceMethods" order="1"/>

<aop:advisor advice-ref="txAdvice"  pointcut-ref="serviceMethods"  order="2"/>

7.在service需要切换数据源的对象的方法上添加

    
   //因为是查询方法我就没有添加事务的注解@Transactional
   @TargetDataSource(name=TargetDataSource.SLAVE)
    public List<UserWithdrawalRecord> findAll() {
        List<UserWithdrawalRecord> all = userWithdrawalDao.findAll();
        return all;
    }

在这里插入图片描述

总结:

1.xml拦截到数据源名称

2.执行切面DataSourceExchange中的before方法,将数据源名称放入 DataSourceHolder中

3.Spring 调用determineCurrentLookupKey()方法<DynamicDataSource中重写AbstractRoutingDataSource类中的方法> ,从DataSourceHolder取出当前的数据库名称,并返回

4.AbstractRoutingDataSource类中determineTargetDataSource()方法调用determineCurrentLookupKey()匹配到指定的数据库,并建立链接,即为切换到相应的数据库;

5.在指定的数据库中执行相应的sql

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/83893.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!