Preface
This article mainly records how spring supports things, and how can it simply implement the database's things functions when Spring combines mybatis. I won't say much about it below, let's take a look at the detailed introduction together.
case1: Things support situation in two tables
First prepare two tables, one user table and one story table, the structure is as follows
CREATE TABLE `user` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL DEFAULT '' COMMENT 'Username', `pwd` varchar(26) NOT NULL DEFAULT '' COMMENT 'password', `isDeleted` tinyint(1) NOT NULL DEFAULT '0', `created` varchar(13) NOT NULL DEFAULT '0', `updated` varchar(13) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `name` (`name`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;CREATE TABLE `story` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `userId` int(20) unsigned NOT NULL DEFAULT '0' COMMENT 'author' userID', `name` varchar(20) NOT NULL DEFAULT '' COMMENT 'Author', `title` varchar(26) NOT NULL DEFAULT '' COMMENT 'password', `story` text COMMENT 'Story content', `isDeleted` tinyint(1) NOT NULL DEFAULT '0', `created` varchar(13) NOT NULL DEFAULT '0', `updated` varchar(13) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `userId` (`userId`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Our situation is that when the user modifys the name, the names of both tables need to be modified together, and inconsistencies are not allowed.
case2: single table thing support
Transfer money, one user reduces money, another user increases money
CREATE TABLE `money` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL DEFAULT '' COMMENT 'Username', `money` int(26) NOT NULL DEFAULT '0' COMMENT 'money', `isDeleted` tinyint(1) NOT NULL DEFAULT '0', `created` varchar(13) NOT NULL DEFAULT '0', `updated` varchar(13) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `name` (`name`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Compared to the above case, this is simpler. The following examples are mainly explained based on this. As for case1, it is left to be expanded.
First, implement the corresponding dao and entity
@Datapublic class MoneyEntity implements Serializable { private static final long serialVersionUID = -7074788842783160025L; private int id; private String name; private int money; private int isDeleted; private int created; private int updated;}public interface MoneyDao { MoneyEntity queryMoney(@Param("id") int userId); // Add money, when negative, it means reducing money int incrementMoney(@Param("id") int userId, @Param("addMoney") int addMoney);}The corresponding mapper file is
<?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.git.hui.demo.mybatis.mapper.MoneyDao"> <sql id="moneyEntity"> id, `name`, `money`, `isDeleted`, `created`, `updated` </sql> <select id="queryMoney" resultType="com.git.hui.demo.mybatis.entity.MoneyEntity"> select <include refid="moneyEntity"/> from money where id=#{id} </select> <update id="incrementMoney"> update money set money=money + #{addMoney} where id=#{id} </update></mapper>The corresponding configuration of mybatis connection data source
<bean> <property name="locations"> <value>classpath*:jdbc.properties</value> </property></bean><bean id="dataSource" init-method="init" destroy-method="close"> <property name="driverClassName" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> <property name="filters" value="stat"/> <property name="maxActive" value="20"/> <property name="initialSize" value="1"/> <property name="maxWait" value="60000"/> <property name="minIdle" value="1"/> <property name="timeBetweenEvictionRunsMillis" value="60000"/> <property name="minEvictableIdleTimeMillis" value="300000"/> <property name="validationQuery" value="SELECT 'x'"/> <property name="testWhileIdle" value="true"/> <property name="testOnBorrow" value="false"/> <property name="testOnBorrow" value="false"/> <property name="testOnReturn" value="false"/> <property name="poolPreparedStatements" value="true"/> <property name="maxPoolPreparedStatementPerConnectionSize" value="50"/></bean><bean id="sqlSessionFactory"> <property name="dataSource" ref="dataSource"/> <!-- Specify mapper file--> <property name="mapperLocations" value="classpath*:mapper/*.xml"/></bean><!-- Specify scan dao --><bean> <property name="basePackage" value="com.git.hui.demo.mybatis"/></bean>Through online query, there are four ways to manage Spring things. Here are demonstrations one by one, how to play each method, and then see how to choose in the actual project
Programming thing management, which realizes the thing management of multiple db operations through TransactionTemplate
a. Implementation
Then, our transfer case can be implemented as follows
@Repositorypublic class CodeDemo1 { @Autowired private MoneyDao moneyDao; @Autowired private TransactionTemplate transactionTemplate; /** * Transfer* * @param inUserId * @param outUserId * @param payMoney * @param status 0 indicates normal transfer, 1 indicates an exception is thrown internally, 2 indicates a new thread, modify the money of inUserId + 200, 3 indicates a new thread, modify the money of outUserId + 200 */ public void transform(final int inUserId, final int outUserId, final int payMoney, final int status) { transactionTemplate.execute(new TransactionCallbackWithoutResult() { protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) { MoneyEntity entity = moneyDao.queryMoney(outUserId); if (entity.getMoney() > payMoney) { // You can transfer money// Reduce money first moneyDao.incrementMoney(outUserId, -payMoney); testCase(inUserId, outUserId, status); // Add money to moneyDao.incrementMoney(inUserId, payMoney); System.out.println("Transfer completed! now: " + System.currentTimeMillis()); } } }); } // The following are all related test cases private void testCase(final int inUserId, final int outUserId, final int status) { if (status == 1) { throw new IllegalArgumentException("Transfer exception!!!"); } else if(status == 2) { addMoney(inUserId); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } else if (status == 3) { addMoney(outUserId); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } } public void addMoney(final int userId) { System.out.printf("Internally addMoney: " + System.currentTimeMillis()); new Thread(new Runnable() { public void run() { moneyDao.incrementMoney(userId, 200); System.out.println(" sub modify success! now: " + System.currentTimeMillis()); } }).start(); }}Mainly look at the above transform method. The encapsulation of things is realized through transactionTemplate internally. There are three db operations internally, one query and two updates. The specific analysis will be explained later.
The above code is relatively simple. The only thing you need to pay attention to is how the transactionTemplate bean is defined. If you don't paste the xml file and the previous ones, just paste the key code. One is the TransactionManager created based on DataSource, and the other is the TransactionTemplate created based on TransactionManager.
<!--Programming Things--><bean id="transactionManager"> <property name="dataSource" ref="dataSource"/></bean><bean id="transactionTemplate"> <property name="transactionManager" ref="transactionManager"/></bean>
b. Test cases
Normal demonstration situation, the demonstration has no exceptions, and the concurrency situation is not considered
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration({"classpath*:spring/service.xml", "classpath*:test-datasource1.xml"})public class CodeDemo1Test { @Autowired private CodeDemo1 codeDemo1; @Autowired private MoneyDao moneyDao; @Test public void testTransfor() { System.out.println("----------------------"); System.out.println("id: 1 money = " + moneyDao.queryMoney(1).getMoney()); System.out.println("id: 2 money = " + moneyDao.queryMoney(2).getMoney()); codeDemo1.transfor(1, 2, 10, 0); System.out.println("-----------------------"); System.out.println("id: 1 money = " + moneyDao.queryMoney(1).getMoney()); System.out.println("id: 2 money = " + moneyDao.queryMoney(2).getMoney()); }}The output is as follows, there is no problem with the money of both accounts
--------------------------------------------------------------------------------------------------------------------------------
id: 1 money = 10000
id: 2 money = 50000
The transfer is completed! now: 1526130394266
--------------------------------------------------------------------------------------------------------------------------------
id: 1 money = 10010
id: 2 money = 49990
An abnormality occurs during the transfer process, especially when the transferor has deducted the money and the recipient has not received the money, that is, the situation where the status in the case is 1.
// The internal exception throwing @Testpublic void testTransforException() { System.out.println("----------------------"); System.out.println("id: 1 money = " + moneyDao.queryMoney(1).getMoney()); System.out.println("id: 2 money = " + moneyDao.queryMoney(2).getMoney()); try { codeDemo1.transfor(1, 2, 10, 1); } catch (Exception e) { e.printStackTrace(); } System.out.println("---------------------------"); System.out.println("id: 1 money = " + moneyDao.queryMoney(1).getMoney()); System.out.println("id: 2 money = " + moneyDao.queryMoney(2).getMoney());}In this regard, we hope to return the money from the transferor, and output it as follows. We found that neither of the money has changed.
--------------------------------------------------------------------------------------------------------------------------------
id: 1 money = 10010
id: 2 money = 49990
--------------------------------------------------------------------------------------------------------------------------------
id: 1 money = 10010
java.lang.IllegalArgumentException: Transfer exception!!!
... // Omit exception information
id: 2 money = 49990
When status is 2, it means that between the transferor's money has been deducted and the payee's money has not been received, someone transferred 200 to the payee. At this time, according to the locking mechanism of mysql, the transfer of the other person should be immediately received (because the payee's account is not locked), and there should be no problem with the amount.
The output result is as follows:
--------------------------------------------------------------------------------------------------------------------------------
id: 1 money = 10010
id: 2 money = 49990
## On the right is a note: During the transfer process, the money was deposited immediately, and the money was added inside is not locked: 1526130827480
sub modify success! now: 1526130827500
## The transfer is completed after saving money! Now: 1526130830488
--------------------------------------------------------------------------------------------------------------------------------
id: 1 money = 10220
id: 2 money = 49980
When the status is 3, it means that the transferor's money has been deducted and the payee's money has not been received, and someone transferred 200 to the transferor. At this time, because the transferor's record and write lock are added, he can only wait for the transfer to submit the transfer to succeed before the +200 success. Of course, the final amount must also be the same.
The output result is as follows
--------------------------------------------------------------------------------------------------------------------------------
id: 1 money = 10220
id: 2 money = 49980
## On the right is a note: I saved money internally, but it didn't succeed immediately
## It is not until the transfer is completed that it is immediately successful. Pay attention to adding money to two timestamps: 1526131101046
The transfer is completed! Now: 1526131104051
sub modify success! now: 1526131104053
--------------------------------------------------------------------------------------------------------------------------------
id: 1 money = 10230
id: 2 money = 50170
c. Summary
So far, programming things have been demonstrated in an example. From the above process, it gives people the same feeling as writing things related to SQL.
start transaction;
-- This is the logic inside the TransactionTemplate#execute method
-- That is, a set of SQL that requires things to be managedcommit;
The next three are declarative thing management, which is less used because each thing management class needs to be added to a TransactionProxyFactoryBean
a. Implementation
In addition to killing TransactionTemplate and removing the internal SQL logic, compared with the previous one, I found that there is basically no difference.
public class FactoryBeanDemo2 { @Autowired private MoneyDao moneyDao; /** * Transfer* * @param inUserId * @param outUserId * @param payMoney * @param status 0 indicates normal transfer, 1 indicates an exception was thrown internally, 2 indicates a new thread, modify the money of inUserId + 200, 3 indicates a new thread, modify the money of outUserId + 200 */ public void transfer(final int inUserId, final int outUserId, final int payMoney, final int status) { MoneyEntity entity = moneyDao.queryMoney(outUserId); if (entity.getMoney() > payMoney) { // You can transfer money// Reduce money first moneyDao.incrementMoney(outUserId, -payMoney); testCase(inUserId, outUserId, status); // Add money to moneyDao.incrementMoney(inUserId, payMoney); System.out.println("Transfer is completed! Now: " + System.currentTimeMillis()); } } private void testCase(final int inUserId, final int outUserId, final int status) { if (status == 1) { throw new IllegalArgumentException("Transfer Exception!!!"); } else if (status == 2) { addMoney(inUserId); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } else if (status == 3) { addMoney(outUserId); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } } public void addMoney(final int userId) { System.out.println("Internal add money: " + System.currentTimeMillis()); new Thread(new Runnable() { public void run() { moneyDao.incrementMoney(userId, 200); System.out.println("sub modify success! now: " + System.currentTimeMillis()); } }).start(); }}The key point is that we need to configure a TransactionProxyBeanFactory. We know that BeanFactory is a means for us to create a Bean by ourselves. The related xml configuration is as follows
<!--Programming things--><bean id="transactionManager"> <property name="dataSource" ref="dataSource"/></bean><bean id="factoryBeanDemo2"/><!-- Configure the proxy for the business layer--><bean id="factoryBeanDemoProxy"> <!-- Configure the target object--> <property name="target" ref="factoryBeanDemo2" /> <!-- Inject the transaction manager--> <property name="transactionManager" ref="transactionManager"/> <!-- Inject the property of the transaction--> <property name="transactionAttributes"> <props> <!-- prop format: * PROPAGATION : Transaction propagation behavior* ISOTATION : Transaction isolation level* readOnly : Read-only * -EXCEPTION : Which exceptions roll back transactions* +EXCEPTION : Which exceptions do not roll back transactions--> <!-- This key corresponds to the methods in the target class--> <prop key="transfor">PROPAGATION_REQUIRED</prop> <!-- <prop key="transfer">PROPAGATION_REQUIRED,readOnly</prop> --> <!-- <prop key="transfer">PROPAGATION_REQUIRED,readOnly</prop> --> <!-- <prop key="transfer">PROPAGATION_REQUIRED,+java.lang.ArithmeticException</prop> --> </props> </property></bean>
Through the above configuration, we can roughly understand that TransactionProxyFactoryBean creates a proxy class of FactoryBeanDemo2. This proxy class encapsulates the logic related to good things inside, which can be regarded as a simple general abstraction of the previous programming.
b. Test
The test code is basically the same as before. The only difference is that we should use the bean generated by the BeanFactory above, rather than directly using FactoryBeanDemo2
Normal demo case:
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration({"classpath*:spring/service.xml", "classpath*:test-datasource2.xml"})public class FactoryBeanDemo1Test { @Resource(name = "factoryBeanDemoProxy") private FactoryBeanDemo2 factoryBeanDemo2; @Autowired private MoneyDao moneyDao; @Test public void testTransfor() { System.out.println("--------------------------"); System.out.println("id: 1 money = " + moneyDao.queryMoney(1).getMoney()); System.out.println("id: 2 money = " + moneyDao.queryMoney(2).getMoney()); factoryBeanDemo2.transfor(1, 2, 10, 0); System.out.println("------------------------------"); System.out.println("id: 1 money = " + moneyDao.queryMoney(1).getMoney()); System.out.println("id: 2 money = " + moneyDao.queryMoney(2).getMoney()); }}Output
--------------------------------------------------------------------------------------------------------------------------------
id: 1 money = 10000
id: 2 money = 50000
The transfer is completed! now: 1526132058886
--------------------------------------------------------------------------------------------------------------------------------
id: 1 money = 10010
id: 2 money = 49990
If the status is 1, and the internal exception is not the case, we hope there will be no problem with the money.
@Testpublic void testTransforException() { System.out.println("---------------------------"); System.out.println("id: 1 money = " + moneyDao.queryMoney(1).getMoney()); System.out.println("id: 2 money = " + moneyDao.queryMoney(2).getMoney()); try { factoryBeanDemo2.transfor(1, 2, 10, 1); } catch (Exception e) { System.out.println(e.getMessage());; } System.out.println("--------------------------"); System.out.println("id: 1 money = " + moneyDao.queryMoney(1).getMoney()); System.out.println("id: 2 money = " + moneyDao.queryMoney(2).getMoney());}The output is
--------------------------------------------------------------------------------------------------------------------------------
id: 1 money = 10010
id: 2 money = 49990
Transfer abnormality!!!
--------------------------------------------------------------------------------------------------------------------------------
id: 1 money = 10010
id: 2 money = 49990
When status is 2, the analysis result should be the same as above, and the output is as follows
--------------------------------------------------------------------------------------------------------------------------------
id: 1 money = 10010
id: 2 money = 49950
Internal money: 1526133325376
sub modify success! now: 1526133325387
Transfer is completed! Now: 1526133328381
--------------------------------------------------------------------------------------------------------------------------------
id: 1 money = 10220
id: 2 money = 49940
When status is 3, the output
--------------------------------------------------------------------------------------------------------------------------------
id: 1 money = 10220
id: 2 money = 49940
Internal money: 1526133373466
The transfer is completed! Now: 1526133376476
sub modify success! now: 1526133376480
--------------------------------------------------------------------------------------------------------------------------------
id: 1 money = 10230
id: 2 money = 50130
c. Summary
The idea of TransactionProxyFactoryBean is to use the proxy mode to implement thing management, generate a proxy class, intercept target methods, and encapsulate a set of SQL operations into things; compared with hard code, it is non-invasive, and supports flexible configuration methods.
The disadvantages are also obvious, each need to be configured, which is quite complicated
Spring has two major characteristics: IoC and AOP. For things like this, can we use AOP to do it?
For methods that need to be turned on, intercept, start things before execution, submit things after execution, and roll back when an exception occurs.
From this perspective, it feels quite promising, and the following two postures are played in this way, so the dependence of aspect is needed.
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.7</version></dependency>
a. Implementation
The java class is exactly the same as the second type, and only the xml changes
<!-- First add the namespace -->xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="... http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"<!-- Corresponding thing notification and section configuration--><tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- Propagation : Transaction propagation behavior isolation : Transaction isolation level read-only : Read-only rollback-for: Which exceptions have occurred no-rollback-for: Which exceptions have occurred not rollback timeout : Expiration information --> <tx:method name="transfor" propagation="REQUIRED"/> </tx:attributes></tx:advice><!-- Configuration section-><aop:config> <!-- Configuration point-cut--> <aop:pointcut expression="execution(* com.git.hui.demo.mybatis.repository.transaction.XmlDemo3.*(..))" id="pointcut1"/> <!-- Configuration section--> <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/></aop:config>
Observe the above configuration and think about the second method. The idea is almost the same, but this method is obviously more general. Through the section and the point of cutting, a large number of configurations can be reduced.
b. Test
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration({"classpath*:spring/service.xml", "classpath*:test-datasource3.xml"})public class XmlBeanTest { @Autowired private XmlDemo3 xmlDemo; @Autowired private MoneyDao moneyDao; @Test public void testTransfor() { System.out.println("---------------------"); System.out.println("id: 1 money = " + moneyDao.queryMoney(1).getMoney()); System.out.println("id: 2 money = " + moneyDao.queryMoney(2).getMoney()); xmlDemo.transfor(1, 2, 10, 0); System.out.println("------------------------"); System.out.println("id: 1 money = " + moneyDao.queryMoney(1).getMoney()); System.out.println("id: 2 money = " + moneyDao.queryMoney(2).getMoney()); }}This test is no different from the general writing method, and is simpler than the injection method of the second FactoryBean
Normal output
--------------------------------------------------------------------------------------------------------------------------------
id: 1 money = 10000
id: 2 money = 50000
The transfer is completed! Now: 1526135301273
--------------------------------------------------------------------------------------------------------------------------------
id: 1 money = 10010
id: 2 money = 49990
status=1 When an exception occurs, the output is
--------------------------------------------------------------------------------------------------------------------------------
id: 1 money = 10010
id: 2 money = 49990
Transfer abnormality!!!
--------------------------------------------------------------------------------------------------------------------------------
id: 1 money = 10010
id: 2 money = 49990
status=2 The scenario of saving money during the transfer process, the output is consistent with the previous expectations.
--------------------------------------------------------------------------------------------------------------------------------
id: 1 money = 10010
id: 2 money = 49990
Internal money: 1526135438403
sub modify success! now: 1526135438421
The transfer is completed! Now: 1526135441410
--------------------------------------------------------------------------------------------------------------------------------
id: 1 money = 10220
id: 2 money = 49980
The output of status=3 is consistent with the previous expectations
--------------------------------------------------------------------------------------------------------------------------------
id: 1 money = 10220
id: 2 money = 49980
Internal money: 1526135464341
The transfer is completed! now: 1526135467349
sub modify success! now: 1526135467352
--------------------------------------------------------------------------------------------------------------------------------
id: 1 money = 10230
id: 2 money = 50170
This is to eliminate the xml and use annotations to do it, which is to replace the configuration in the previous xml with the @Transactional annotation.
a. Implementation
@Repositorypublic class AnnoDemo4 { @Autowired private MoneyDao moneyDao; /** * Transfer* * @param inUserId * @param outUserId * @param payMoney * @param status 0 indicates normal transfer, 1 indicates an exception is thrown internally, 2 indicates a new thread, modify the money of inUserId + 200, 3 indicates a new thread, modify the money of outUserId + 200 * * * Propagation in Transactional annotation: Transaction propagation behavior isolation: Transaction isolation level readOnly: Read-only* rollbackFor :What exceptions have occurred noRollbackFor :What exceptions have occurred not to rollback* rollbackForClassName Rollback according to the exception class name*/ @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false) public void transform(final int inUserId, final int outUserId, final int payMoney, final int status) { MoneyEntity entity = moneyDao.queryMoney(outUserId); if (entity.getMoney() > payMoney) { // You can transfer money // Reduce money first moneyDao.incrementMoney(outUserId, -payMoney); testCase(inUserId, outUserId, status); // Add money to moneyDao.incrementMoney(inUserId, payMoney); System.out.println("Transfer is completed! now: " + System.currentTimeMillis()); } } private void testCase(final int inUserId, final int outUserId, final int status) { if (status == 1) { throw new IllegalArgumentException("Transfer Exception!!!"); } else if (status == 2) { addMoney(inUserId); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } else if (status == 3) { addMoney(outUserId); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } } private void addMoney(final int userId) { System.out.println("Inside add money: " + System.currentTimeMillis()); new Thread(new Runnable() { public void run() { moneyDao.incrementMoney(userId, 200); System.out.println("sub modify success! now: " + System.currentTimeMillis()); } }).start(); }}Therefore, it is necessary to configure it in xml to enable the annotation of things
<!--Programming Things--><bean id="transactionManager"> <property name="dataSource" ref="dataSource"/></bean><tx:annotation-driven transaction-manager="transactionManager"/>
This makes it clearer. In actual projects, xml and annotation methods are also the most commonly used scenarios.
b. Test case
It is exactly the same as the third test case, and the output result is the same, and it is omitted directly
The above talks about four ways to use things in Spring. Among them, the hard-code method may be the best understanding, which is equivalent to directly translating the method of using things in SQL into the corresponding Java code; and the FactoryBean method is equivalent to treating special situations and treating each thing with a proxy class to enhance the function of things; the latter two principles are almost implemented using thing notification (AOP) to define tangent points and related information.
Programming:
transactionTemplate#execute methodProxy BeanFactory:
xml configuration:
Annotation method:
tx:annotation-driven transaction-manager="transactionManager"/>document
Four ways of Spring transaction management
Source code
The above is the entire content of this article. I hope that the content of this article has certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support to Wulin.com.