Spring+Mybatis+Mysql搭建分布式數(shù)據(jù)庫訪問框架的方法
一、前言
用Java開發(fā)企業(yè)應(yīng)用軟件, 經(jīng)常會(huì)采用Spring+MyBatis+Mysql搭建數(shù)據(jù)庫框架。如果數(shù)據(jù)量很大,一個(gè)MYSQL庫存儲(chǔ)數(shù)據(jù)訪問效率很低,往往會(huì)采用分庫存儲(chǔ)管理的方式。本文講述如何通過Spring+Mybatis構(gòu)建多數(shù)據(jù)庫訪問的架構(gòu),并采用多線程提升數(shù)據(jù)庫的訪問效率。
需要說明一下,這種方式只適合數(shù)據(jù)庫數(shù)量、名稱固定,且不是特別多的情況。針對(duì)數(shù)據(jù)庫數(shù)量不固定的情況,后面再寫一篇處理方案。
二、整體方案

三、開發(fā)環(huán)境準(zhǔn)備
3.1 下載Spring、Mybatis、Mysql組件。
3.2 Eclipse:Java開發(fā)IDE。引入如下jar包:

代碼結(jié)構(gòu)如下:

四、構(gòu)建數(shù)據(jù)庫集群
在MYSQL中創(chuàng)建11個(gè)數(shù)據(jù)庫(test1/2/3/4/5/6/7/8/9/10/11)創(chuàng)建一個(gè)簡單的表:

在test1的tbl_Demo表中插入5千萬條數(shù)據(jù),其它10個(gè)數(shù)據(jù)庫的tbl_Demo表中分別插入5百萬條數(shù)據(jù)(用函數(shù))。
在test1的tbl_Demo表中插入5千萬條數(shù)據(jù),其它10個(gè)數(shù)據(jù)庫的tbl_Demo表中分別插入5百萬條數(shù)據(jù)(用函數(shù))。
五、創(chuàng)建Mybatis數(shù)據(jù)庫映射接口
/**
* Mybatis 映射接口
*
*
* @author elon
* @version 1.0, 2015年10月23日
*/
public interface IDemo
{
public void insertDemo(DemoDAO demo);
public List<Integer> selectGroup();
}
/**
*
* Mybatis 映射服務(wù)接口
*
* @author elon
* @version 1.0, 2015年10月23日
*/
public interface IDemoService
{
public void insertDemo(DemoDAO demo);
public List<Integer> selectGroup();
}
/**
*
* Mybatis 映射服務(wù)實(shí)現(xiàn)
*
* @author elon
* @version 1.0, 2015年10月23日
*/
public class DemoServiceImpl implements IDemoService
{
private IDemo idemo = null;
public void setIdemo(IDemo idemo) {
this.idemo = idemo;
}
@Override
public void insertDemo(DemoDAO demo)
{
idemo.insertDemo(demo);
}
@Override
public List<Integer> selectGroup()
{
return idemo.selectGroup();
}
}
六、創(chuàng)建數(shù)據(jù)庫標(biāo)識(shí)管理和動(dòng)態(tài)數(shù)據(jù)源
/**
*
* 保存數(shù)據(jù)庫標(biāo)識(shí)。每個(gè)線程由獨(dú)立的對(duì)象存儲(chǔ)
*
* @author elon
* @version 1.0, 2015年10月23日
*/
public class DBIndetifier
{
private static ThreadLocal<String> dbKey = new ThreadLocal<String>();
public static void setDBKey(final String dbKeyPara)
{
dbKey.set(dbKeyPara);
}
public static String getDBKey()
{
return dbKey.get();
}
}
/**
*
* 動(dòng)態(tài)數(shù)據(jù)源??筛鶕?jù)不同的數(shù)據(jù)索引連接不同的數(shù)據(jù)庫
*
* @author elon
* @version 1.0, 2015年10月23日
*/
public class DynamicDataSource extends AbstractRoutingDataSource
{
@Override
public Object determineCurrentLookupKey()
{
return DBIndetifier.getDBKey();
}
}
七、創(chuàng)建數(shù)據(jù)庫訪問對(duì)象
/**
*
* 數(shù)據(jù)庫訪問對(duì)象。用于插入數(shù)據(jù)。
*
* @author elon
* @version 1.0, 2015年10月23日
*/
public class DemoDAO
{
private int a;
private String b;
private int c;
public int getA()
{
return a;
}
public void setA(int a)
{
this.a = a;
}
public String getB()
{
return b;
}
public void setB(String b)
{
this.b = b;
}
public int getC()
{
return c;
}
public void setC(int c)
{
this.c = c;
}
}
/**
*
* 映射結(jié)果定義
*
* @author elon
* @version 1.0, 2015年10月23日
*/
public class DemoResult implements Serializable
{
/**
* Comment for <code>serialVersionUID</code><br>
*
*/
private static final long serialVersionUID = -413001138792531448L;
private long sum;
public long getSum()
{
return sum;
}
public void setSum(long sum)
{
this.sum = sum;
}
@Override
public String toString()
{
return String.valueOf(sum);
}
}
八、創(chuàng)建數(shù)據(jù)庫訪問任務(wù)
/**
* 數(shù)據(jù)庫訪問任務(wù)定義。將每一個(gè)對(duì)數(shù)據(jù)庫訪問的請(qǐng)求包裝為一個(gè)任務(wù)對(duì)象,放到任務(wù)管理中,
* 然后等待任務(wù)執(zhí)行完成,取出執(zhí)行結(jié)果。
*
* @author elon
* @version 1.0, 2015年10月23日
*/
public class DBTask implements Runnable
{
// 操作數(shù)據(jù)庫標(biāo)識(shí),用于指定訪問的數(shù)據(jù)庫。與spring配置文件中的數(shù)據(jù)動(dòng)態(tài)數(shù)據(jù)源定義一致。
private final String dbKey;
// mybatis數(shù)據(jù)庫訪問對(duì)象
private final Object dbAccessObject;
// mysbatis數(shù)據(jù)庫訪問方法名稱,用于反射調(diào)用
private final String methodName;
// 存儲(chǔ)可變參數(shù)的值
private final Object[] paraArray;
// 存儲(chǔ)可變參數(shù)類型
@SuppressWarnings("rawtypes")
private final Class[] paraClassArray;
// 數(shù)據(jù)庫操作結(jié)果。查詢操作返回查詢結(jié)果; 插入、刪除、修改操作返回null。
private Object operateResult;
// 操作數(shù)據(jù)庫拋出的異常信息
private Exception exception;
// 標(biāo)識(shí)任務(wù)是否已經(jīng)執(zhí)行
private boolean finish;
/**
* 構(gòu)造函數(shù)
* @param dbKey 數(shù)據(jù)庫標(biāo)識(shí)
* @param dbAccessObject 數(shù)據(jù)庫訪問對(duì)象
* @param methodName 數(shù)據(jù)庫訪問方法名稱
* @param paraArray 參數(shù)列表
*/
public DBTask(final String dbKey, final Object dbAccessObject, final String methodName,
final Object... paraArray)
{
this.dbKey = dbKey;
this.dbAccessObject = dbAccessObject;
this.methodName = methodName;
this.paraArray = paraArray;
finish = false;
exception = null;
paraClassArray = new Class[paraArray.length];
for (int index = 0; index < paraArray.length; ++index)
{
paraClassArray[index] = paraArray[index].getClass();
}
operateResult = null;
}
/**
*
* 任務(wù)執(zhí)行函數(shù)
*
*/
@Override
public void run()
{
try
{
DBIndetifier.setDBKey(dbKey);
Method method = dbAccessObject.getClass().getMethod(methodName, paraClassArray);
// 查詢操作返回查詢結(jié)果; 插入、刪除、修改操作返回null
operateResult = method.invoke(dbAccessObject, paraArray);
}
catch (Exception e)
{
exception = e;
e.printStackTrace();
}
finish = true;
}
/**
*
* 返回操作結(jié)果。查詢操作返回查詢結(jié)果; 插入、刪除、修改操作返回null
*
* @return 操作結(jié)果
*/
public Object getRetValue()
{
return operateResult;
}
/**
* 拋出數(shù)據(jù)庫操作異常
*
* @return 異常
*/
public Exception getException()
{
return exception;
}
/**
*
* 返回任務(wù)是否已執(zhí)行
*
* @return 標(biāo)記
*/
public boolean isFinish()
{
return finish;
}
}
九、創(chuàng)建數(shù)據(jù)庫任務(wù)管理器
/**
* 數(shù)據(jù)庫訪問任務(wù)管理。將數(shù)據(jù)庫訪問任務(wù)放到線程池中執(zhí)行。
*
*
* @author elon
* @version 1.0, 2015年10月23日
*/
public class DBTaskMgr
{
private static class DBTaskMgrInstance
{
public static final DBTaskMgr instance = new DBTaskMgr();
}
public static DBTaskMgr instance()
{
return DBTaskMgrInstance.instance;
}
private ThreadPoolExecutor pool;
public DBTaskMgr()
{
pool = new ThreadPoolExecutor(10, 50, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(10000),
new ThreadPoolExecutor.CallerRunsPolicy());
}
public void excute(Runnable task)
{
pool.execute(task);
}
}
十、創(chuàng)建MyBatis配置文件
10.1 mybatis.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <mappers> <mapper resource="cfg/demoMapper.xml"/> </mappers> </configuration>
10.2 demoMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.elon.IDemo">
<insert id="insertDemo" parameterType="com.elon.DemoDAO">
insert into tbl_demo(a, b, c) values(#{a}, #, #{c});
</insert>
<resultMap id="demoResult" type="com.elon.DemoResult">
<id property="sum" column="sumColum"/>
</resultMap>
<select id="selectGroup" resultMap="demoResult">
select sum(a) as sumColum from tbl_demo group by c;
</select>
</mapper>
十一、創(chuàng)建Spring配置文件
11.1 spring.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-3.0.xsd">
<bean id="dataSource_1" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://10.70.69.69:3306/test1"></property>
<property name="username" value="user123"></property>
<property name="password" value="user123"></property>
<property name="maxActive" value="100"></property>
<property name="maxIdle" value="30"></property>
<property name="maxWait" value="500"></property>
<property name="defaultAutoCommit" value="true"></property>
</bean>
<bean id="dataSource_2" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://10.70.69.69:3306/test2"></property>
<property name="username" value="user123"></property>
<property name="password" value="user123"></property>
<property name="maxActive" value="100"></property>
<property name="maxIdle" value="30"></property>
<property name="maxWait" value="500"></property>
<property name="defaultAutoCommit" value="true"></property>
</bean>
<bean id="dataSource_3" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://10.70.69.69:3306/test3"></property>
<property name="username" value="user123"></property>
<property name="password" value="user123"></property>
<property name="maxActive" value="100"></property>
<property name="maxIdle" value="30"></property>
<property name="maxWait" value="500"></property>
<property name="defaultAutoCommit" value="true"></property>
</bean>
<bean id="dataSource_4" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://10.70.69.69:3306/test4"></property>
<property name="username" value="user123"></property>
<property name="password" value="user123"></property>
<property name="maxActive" value="100"></property>
<property name="maxIdle" value="30"></property>
<property name="maxWait" value="500"></property>
<property name="defaultAutoCommit" value="true"></property>
</bean>
<bean id="dataSource_5" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://10.70.69.69:3306/test5"></property>
<property name="username" value="user123"></property>
<property name="password" value="user123"></property>
<property name="maxActive" value="100"></property>
<property name="maxIdle" value="30"></property>
<property name="maxWait" value="500"></property>
<property name="defaultAutoCommit" value="true"></property>
</bean>
<bean id="dataSource_6" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://10.70.69.69:3306/test6"></property>
<property name="username" value="user123"></property>
<property name="password" value="user123"></property>
<property name="maxActive" value="100"></property>
<property name="maxIdle" value="30"></property>
<property name="maxWait" value="500"></property>
<property name="defaultAutoCommit" value="true"></property>
</bean>
<bean id="dataSource_7" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://10.61.67.246:3306/test7"></property>
<property name="username" value="user123"></property>
<property name="password" value="user123"></property>
<property name="maxActive" value="100"></property>
<property name="maxIdle" value="30"></property>
<property name="maxWait" value="500"></property>
<property name="defaultAutoCommit" value="true"></property>
</bean>
<bean id="dataSource_8" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://10.61.67.246:3306/test8"></property>
<property name="username" value="user123"></property>
<property name="password" value="user123"></property>
<property name="maxActive" value="100"></property>
<property name="maxIdle" value="30"></property>
<property name="maxWait" value="500"></property>
<property name="defaultAutoCommit" value="true"></property>
</bean>
<bean id="dataSource_9" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://10.61.67.246:3306/test9"></property>
<property name="username" value="user123"></property>
<property name="password" value="user123"></property>
<property name="maxActive" value="100"></property>
<property name="maxIdle" value="30"></property>
<property name="maxWait" value="500"></property>
<property name="defaultAutoCommit" value="true"></property>
</bean>
<bean id="dataSource_10" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://10.61.67.246:3306/test10"></property>
<property name="username" value="user123"></property>
<property name="password" value="user123"></property>
<property name="maxActive" value="100"></property>
<property name="maxIdle" value="30"></property>
<property name="maxWait" value="500"></property>
<property name="defaultAutoCommit" value="true"></property>
</bean>
<bean id="dataSource_11" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://10.61.67.246:3306/test11"></property>
<property name="username" value="user123"></property>
<property name="password" value="user123"></property>
<property name="maxActive" value="100"></property>
<property name="maxIdle" value="30"></property>
<property name="maxWait" value="500"></property>
<property name="defaultAutoCommit" value="true"></property>
</bean>
<bean id="dataSource" class="com.elon.DynamicDataSource">
<property name="targetDataSources">
<map>
<entry key="test1" value-ref="dataSource_1"/>
<entry key="test2" value-ref="dataSource_2"/>
<entry key="test3" value-ref="dataSource_3"/>
<entry key="test4" value-ref="dataSource_4"/>
<entry key="test5" value-ref="dataSource_5"/>
<entry key="test6" value-ref="dataSource_6"/>
<entry key="test7" value-ref="dataSource_7"/>
<entry key="test8" value-ref="dataSource_8"/>
<entry key="test9" value-ref="dataSource_9"/>
<entry key="test10" value-ref="dataSource_10"/>
<entry key="test11" value-ref="dataSource_11"/>
</map>
</property>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:cfg/mybatis.xml"></property>
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="iDemo" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.elon.IDemo"></property>
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>
<bean id="iDemoService" class="com.elon.DemoServiceImpl">
<property name="idemo" ref="iDemo"></property>
</bean>
</beans>
十二、測(cè)試代碼
public class TestMain
{
/**
* 測(cè)試代碼
*
*
* @param args
*/
public static void main(String[] args)
{
@SuppressWarnings("resource")
ApplicationContext context = new ClassPathXmlApplicationContext("cfg/spring.xml");
IDemoService service1 = (IDemoService)context.getBean("iDemoService");
// 創(chuàng)建任務(wù)對(duì)象
DBTask task1 = new DBTask("test1", service1, "selectGroup");
DBTask task2 = new DBTask("test2", service1, "selectGroup");
DBTask task3 = new DBTask("test3", service1, "selectGroup");
DBTask task4 = new DBTask("test4", service1, "selectGroup");
DBTask task5 = new DBTask("test5", service1, "selectGroup");
DBTask task6 = new DBTask("test6", service1, "selectGroup");
DBTask task7 = new DBTask("test7", service1, "selectGroup");
DBTask task8 = new DBTask("test8", service1, "selectGroup");
DBTask task9 = new DBTask("test9", service1, "selectGroup");
DBTask task10 = new DBTask("test10", service1, "selectGroup");
DBTask task11 = new DBTask("test11", service1, "selectGroup");
DemoDAO demo = new DemoDAO();
demo.setA(10000000);
demo.setB("12121212");
demo.setC(100);
DBTask taskInsert = new DBTask("test2", service1, "insertDemo", demo);
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("開始插入數(shù)據(jù):" + format.format(new Date()));
DBTaskMgr.instance().excute(taskInsert);
while (true)
{
if (!taskInsert.isFinish())
{
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
else
{
break;
}
}
System.out.println("插入數(shù)據(jù)結(jié)束:" + format.format(new Date()));
System.out.println("開始查詢5千萬數(shù)據(jù)表:" + format.format(new Date()));
DBTaskMgr.instance().excute(task1);
while (true)
{
if (!task1.isFinish())
{
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
else
{
break;
}
}
System.out.println(task1.getRetValue());
System.out.println("查詢5千萬數(shù)據(jù)表結(jié)束:" + format.format(new Date()));
List<DBTask> taskList = new ArrayList<DBTask>();
taskList.add(task2);
taskList.add(task3);
taskList.add(task4);
taskList.add(task5);
taskList.add(task6);
taskList.add(task7);
taskList.add(task8);
taskList.add(task9);
taskList.add(task10);
taskList.add(task11);
System.out.println("開始查詢10個(gè)5百萬數(shù)據(jù)表:" + format.format(new Date()));
for (DBTask task : taskList)
{
DBTaskMgr.instance().excute(task);
}
while (true)
{
int success = 0;
for (DBTask task : taskList)
{
if (!task.isFinish())
{
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
else
{
++success;
}
}
if (success == 10)
{
break;
}
}
for (DBTask task : taskList)
{
System.out.println(task.getRetValue());;
}
System.out.println("10個(gè)5百萬數(shù)據(jù)表查詢結(jié)束:" +format.format(new Date()));
}
}
十三、測(cè)試結(jié)果

直接查詢一個(gè)5千萬條數(shù)據(jù)的數(shù)據(jù)庫用時(shí):45s。
多線程同步查詢10個(gè)5百萬數(shù)據(jù)的數(shù)據(jù)庫用時(shí): 22s。
由于10個(gè)數(shù)據(jù)庫放在兩臺(tái)服務(wù)器上,一個(gè)服務(wù)器5個(gè)數(shù)據(jù)庫。如果將10個(gè)數(shù)據(jù)分別部署到10個(gè)服務(wù)器,效率將更高。
總結(jié)
以上所述是小編給大家介紹的Spring+Mybatis+Mysql搭建分布式數(shù)據(jù)庫訪問框架,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
- 詳解idea搭建springboot+mybatis框架的教程
- 使用IDEA搭建SSM框架的詳細(xì)教程(spring + springMVC +MyBatis)
- 詳解手把手Maven搭建SpringMVC+Spring+MyBatis框架(超級(jí)詳細(xì)版)
- Java框架搭建之Maven、Mybatis、Spring MVC整合搭建(圖文)
- 詳解MyEclipse中搭建spring-boot+mybatis+freemarker框架
- Spring MVC 4.1.3 + MyBatis零基礎(chǔ)搭建Web開發(fā)框架(注解模式)
- Java的MyBatis框架項(xiàng)目搭建與hellow world示例
- Windows下Java+MyBatis框架+MySQL的開發(fā)環(huán)境搭建教程
- MyBatis框架搭建與代碼解讀分析
相關(guān)文章
基于Ok+Rxjava+retrofit實(shí)現(xiàn)斷點(diǎn)續(xù)傳下載
這篇文章主要為大家詳細(xì)介紹了基于Ok+Rxjava+retrofit實(shí)現(xiàn)斷點(diǎn)續(xù)傳下載,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-06-06
SpringBoot整合Security實(shí)現(xiàn)權(quán)限控制框架(案例詳解)
Spring Security是一個(gè)能夠?yàn)榛赟pring的企業(yè)應(yīng)用系統(tǒng)提供聲明式的安全訪問控制解決方案的安全框,是一個(gè)重量級(jí)的安全管理框架,本文給大家介紹的非常詳細(xì),需要的朋友參考下吧2021-08-08
SpringCloud使用Nacos保存和讀取變量的配置方法
在使用SpringCloud開發(fā)微服務(wù)時(shí),經(jīng)常會(huì)遇到一些比較小的后臺(tái)參數(shù)配置,這些配置不足以單獨(dú)開一張表去存儲(chǔ),而且其他服務(wù)會(huì)讀取該參數(shù),這篇文章主要介紹了SpringCloud使用Nacos保存和讀取變量,需要的朋友可以參考下2022-07-07
Maven基礎(chǔ):錯(cuò)誤對(duì)應(yīng):was cached in the local&nbs
這篇文章主要介紹了Maven基礎(chǔ):錯(cuò)誤對(duì)應(yīng):was cached in the local repository的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-03-03
Java C++ leetcode執(zhí)行一次字符串交換能否使兩個(gè)字符串相等
這篇文章主要為大家介紹了Java C++ leetcode1790執(zhí)行一次字符串交換能否使兩個(gè)字符串相等,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
JavaWeb 使用Session實(shí)現(xiàn)一次性驗(yàn)證碼功能
這篇文章主要介紹了JavaWeb 使用Session實(shí)現(xiàn)一次性驗(yàn)證碼功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-08-08

