java transaction, if the database multiple operations, every step is executed or a transaction.
If the database operation is not performed in a step or transaction failure caused by abnormal, so there is no transaction is executed and some were executed, thus there will be a rollback of the transaction, cancel the previous operation .....
Note: The use of transaction processing in Java, first requires that the database supports transactions. If using MySQL transaction functionality, requires the MySQL table type is Innodb only support transactions. Otherwise, the Java program to do a commit or rollback, but did not take effect in the database.
JavaBean way to use JDBC transaction processing
public int delete(int sID) {
? dbc = new DataBaseConnection();
? Connection con = dbc.getConnection();
? try {
?? con.setAutoCommit(false);
??// ??JDBC?????????
?? dbc.executeUpdate("delete from xiao where ID=" + sID);
?? dbc.executeUpdate("delete from xiao_content where ID=" + sID);
?? dbc.executeUpdate("delete from xiao_affix where bylawid=" + sID);
?? con.commit();
??//??JDBC??
?? con.setAutoCommit(true);
??// ??JDBC?????????
?? dbc.close();
?? return 1;
? } ? catch (Exception exc) {
?? con.rollBack();
??//??JDBC??
?? exc.printStackTrace();
?? dbc.close();
?? return -1;
? }
}
in database operations, a transaction is defined by one or more updates to the database sql statement consisting of an indivisible unit of work. Only when the transaction completed all operations are normal, the entire transaction can be committed to the database, if there is an operation does not complete, you have to undo the entire transaction. For example, in the bank transfer transaction, assuming Joe Smith from his own account on the 1000 yuan to John Doe's account, the relevant sql statement is as follows:
update account set monery = monery-1000 where name = 'zhangsan' update account set monery = monery +1000 where name = 'lisi' The two statements must be used as a completed transaction to deal with. Only when the two are successfully performed, in order to submit the transaction. If there is a failure, the entire transaction must be undone.
in connection class provides three control transaction method:
(1) setAutoCommit (Boolean autoCommit): Set whether to automatically commit the transaction;
(2) commit (); commit the transaction;
(3) rollback (); undo affairs;
in jdbc api, the default situation to automatically commit the transaction, that is, each update of the database sql statement on behalf of a transaction, the operation is successful, the system automatically calls the commit () to submit, or they will call rollback () to undo the transaction.
in jdbc api, you can by calling setAutoCommit (false) to disable the auto-commit the transaction. Then you can put more than one sql statement to update the database as a transaction, after the completion of all operations, call the commit () to the overall submission. If one of the sql operation fails, it will not perform commit () method, but rather to produce the corresponding sqlexception, then you can catch the exception code block call rollback () method to undo the transaction.
transactional enterprise applications need to address the most important issues. By providing a complete J2EE JTA transaction management capabilities, including multiple transactional resource management capabilities. But most applications are running on top of a single transactional resources (a database), they do not need global transaction services. Local transaction service is adequate (such as JDBC transaction management).
should not be discussed in what way the transaction, the main purpose is to discuss how to design more elegant affairs services. JDBC transaction only example. Related to the DAO, Factory, Proxy, Decorator pattern concept, etc., please read the relevant information. Maybe you have heard, transaction processing should be done in a service layer, maybe you are doing this, but do you know why? Why not put the DAO layer to do the transaction. Obvious reason is that the business layer interface, each method sometimes is a business case (User Case), it needs to call different DAO objects to complete a business method. Such as online bookstore textbook simply to determine the final order, for example, is the first business method invocation BookDAO object (generally through DAO factory produces), BookDAO determine whether there is inventory balances, obtain the book's price information, and then call CustomerDAO deduct the cost from your account and record information, then other services (to notify the administrator, etc.). Streamline business processes probably so: Note that we ignore the example of the connection process, as long as the same thread is taken to the same connection (available ThreadLocal implementation): ;
first is the service interface for the interface, rather than class programming:
public interface BookStoreManager{
public boolean buyBook(String bookId,int quantity)throws SystemException;
....?????? }
Then there is the service interface implementation class?? Business Objects:
public class BookStoreManagerImpl implements BookStoreManager{
public boolean buyBook(String bookId)throws SystemException{
Connection conn=ConnectionManager.getConnection();
????//???????
boolean b=false;
try{
conn.setAutoCommit(false);
???? //??????
BookDAO bookDAO=DAOFactory.getBookDAO();
CustomerDAO customerDAO=DAOFactory.getCustomerDAO();
//????????
??????????if(BookDAO.reduceInventory(conn,bookId,quantity)){
BigDecimal price=BookDAO.getPrice(bookId); //???
//????????price*quantity???
b=CustomerDAO.reduceAccount(conn,price.multiply(new BigDecimal(quantity));
....
???????????????????.
...
??????conn.commit(); //????
conn.setAutoCommit(true);
}
}catch(SQLException e){
conn.rollback();
?????? //?????????
con.setAutoCommit(true);
e.printStackTrace();
throws new SystemException(e);
}
return b;
}
}
then representative plant:
public final class ManagerFactory {
public static BookStoreManager getBookStoreManager() {
return new BookStoreManagerImpl();
}
}
This design is very suitable for the DAO simple activities, our project is the use of such a small system design, but it does not fit on more large-scale application.
First of all, you have to smell the code duplication of bad smell? Every time setting AutoCommit is false, then submit, abnormal rollback exception thrown upper packing, do not bother to write more strange, it can not eliminate it?
Secondly, the business representative objects within it now knows all the details of transaction management, which we designed the original intention of the object does not match the sales representative. For sales representative objects, understanding a transaction-related business constraints is quite appropriate, but that it is responsible for implementing them are less apposite.
again, have you ever thought nested business objects of the scene? Business representatives call each other between objects, nested layers, and how you deal with it this time? You know by now that we approach every business methods are separate transaction context in which (Transaction Context), calling each other to form a nested transaction, then how should you deal with? Perhaps way is to re-write it again, put into a different business approach focuses on a Big Mac wrapped in a transaction context.
we have a more elegant design to solve these problems, if we Transaction Context control to a representative of the business objects, DAO, and other Component of knowledge of the external object. When an agent object method requires a transaction management, it prompts this external object that it wants to start a transaction, an external object to get a connection and start a database transaction. That is, transaction control, detached from the service layer, service layer when the web layer calls on behalf of a business object, which returns an object through Transaction Context external packaging (or agency) business objects. This proxy object sends the request to the original sales representative objects, but on which the business method for transaction control. So how do we achieve this effect it? The answer is JDK1.3 introduced dynamic proxy technology. Dynamic proxy technology can only proxy interface, which is why we need business interface BookStoreManager causes.
First, we introduce the Transaction Context external object, its code is actually very simple, if you do not understand the dynamic agent technology Read other information.
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import com.strutslet.demo.service.SystemException;
public final class TransactionWrapper {
/** * ????????????????????????????????? */
public static Object decorate(Object delegate) {
return Proxy.newProxyInstance(delegate.getClass().getClassLoader(),
delegate.getClass().getInterfaces(), new XAWrapperHandler(
delegate));
} //??????
static final class XAWrapperHandler implements InvocationHandler {
private final Object delegate;
XAWrapperHandler(Object delegate) {
this.delegate = delegate;
}
?? //????????????????????
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result = null;
Connection con = ConnectionManager.getConnection();
try {
//??????
????con.setAutoCommit(false);
//?????????????
????result = method.invoke(delegate, args);
con.commit(); //????
con.setAutoCommit(true);
} catch (Throwable t) {
//??
????con.rollback();
con.setAutoCommit(true);
throw new SystemException(t);
}
return result;
}
}
}
As we have seen, this object is merely the business object needs to control the business affairs of the transaction control methods section extracted it.
Please note that sales representative within the object method calls itself will not begin a new transaction, because these calls will not be passed to the proxy object.
so, we go addition to representing repeated taste. In this case, the object of our representatives modified:
public class BookStoreManagerImpl implements BookStoreManager {
public boolean buyBook(String bookId)throws SystemException{
Connection conn=ConnectionManager.getConnection();// ???????
boolean b=false;
try{
BookDAO bookDAO=DAOFactory.getBookDAO();
CustomerDAO customerDAO=DAOFactory.getCustomerDAO();
// ????????
?????? if(BookDAO.reduceInventory(conn,bookId,quantity)){
BigDecimal price=BookDAO.getPrice(bookId); // ???
// ????????price*quantity???
????????b= CustomerDAO.reduceAccount(conn,price.multiply(new
????????BigDecimal(quantity));
???? ....
???????????????????????????.
...
?????? }
}catch(SQLException e){
throws new SystemException(e);
}
return b;
}
....
}
you can see, this time focusing on the business representatives to implement business logic objects, it is no longer interested in the details of the transaction control, all of them entrusted to an external object. Representative factories changed a bit, let it return two types of representative objects:
public final class ManagerFactory {
//??????????????????
public static BookStoreManager getBookStoreManagerTrans() {
return (BookStoreManager) TransactionWrapper
.decorate(new BookStoreManagerImpl());
} //????
public static BookStoreManager getBookStoreManager() {
return new BookStoreManagerImpl();
}
......
}
factory on behalf of our business provides a method of generating two different objects: one for creating the wrapped object, it will for each method call Create a new transaction; another one is not used to create the package version, which is used to add to the existing transaction (such as other business on behalf of the business object method) to solve the problem of nested business object represents.
Our elegant design is not enough, for example, all of our sales representatives default object method calls will be wrapped in a Transaction Context. The fact is many ways to deal with the database may not need, if we can configure which method requires a transaction statement, which does not require transaction management even more perfect. Solution is very simple, an XML configuration file to configure these calls can judge. Here, learn about the spring will realize that this is not exactly declarative transaction control it? This is true, transaction control is a service of AOP, spring declarative transaction management is achieved through AOP. AOP implementations include: dynamic proxy technology, the byte code generation techniques (such as CGLIB library), java code generation (early EJB use), modify the class loader as well as the source code level code mixed weaving (aspectj) and so on. Here is the use of a dynamic proxy technology, only the interface agent; against dynamic proxy class can use the concept of simple transactions cglib library
I do not want to explain what in principle is a transaction, it should be too boring. I just want to start with a simple example to illustrate what is a transaction.
For example, we have an order inventory management system, and each time we have to generate orders while inventory reduction. Generally speaking order and inventory in the database is divided into two tables to save: Orders table, inventory table. Each time we add an order actually requires two steps: In order to insert a table of data, and modify the inventory data.
so the question is, for example, we need a unit of 10 orders, inventory has 30, the ideal is that we operate in order to insert a table in units of 10 orders, after inventory data in the table will be revised to 20. Sometimes, however, things are not always happen the way you want, for example: when you modify the inventory database sudden inexplicable reason unable to connect due on. That inventory update failed. But the orders have been generated, then how to do it? No way, only manual modifications. So the best way is to insert the operation orders and inventory to modify the operation of binding together, must succeed or do not do anything. This is business.
Java how to handle matters it? We java.sql.Connection Speaking, Connection represents a link to the database, you can Connection to the database operation. In the usual situation is that the Connection property is automatically submitted, that each operation is really updates the database, really can not back up. For the above example, once the inventory update failed, orders can not be rolled back, because the order is really inserted into the database. This is not what we want.
we want is this: look successful, but did not really operate the database, knowing that I wanted him to really happen. Through the Connection setAutoCommit (false) so that Connection does not automatically submit your data, unless you really want to submit. So how to make it operate really happened? You can use the Connection commit method. How to make it operate back? Use the rollback method.
example:
try{
??Connection conn = getConnection??; // ???????????
??conn.setAutoCommit(false);
??// ???? // ????
?? conn.commit(); // ????????????
??} catch(SQLException ex) {
??conn.rollback(); // ??????????????
??}
??finally {
??conn.close();
??}
It is very important here, the transaction is based on the database link. Therefore, in the case but the database, transaction operation is very simple.
then if table distributed in two different databases it?
example, the Orders table in the order database, inventory table in the library inventory, how do we deal with such matters it?
need to pay attention, you can submit it encounters an error!
try{
Connection conn1 = getConnection1();
Connection conn2 = getConnection2();
// ??conn1?????
// ??conn2?????
try{
conn1.commit??
} catch(SQLExcetion ) {
conn1.rollback();
}
try {
conn2.commit();
} catch(SQLException ) {
conn2.rollbakc();
// ?????????????? }
} catch(SQLException ex) {
// ???????
conn1.rollback
// ???????
conn1.rollback && conn2.rollback
} finally {
conn1.close();
conn2.close();
}
look at the above code, we know, in fact, the operation is very complex, even: just insert delete assurance that orders can not guarantee that.
affairs in such circumstances can be called a distributed transaction, the transaction through the above code submitted simultaneously processing section we can conclude that, in order to handle distributed transactions, there must be independent of the database third-party transaction processing components.
fortunately is often the case, JavaEE compliant application server, for example: Weblogic, Websphere, JBoss, Glassfish and others have such a distributed transaction processing components.
how to use the application server's distributed transaction manager to handle distributed transactions?
to galssfish example
1 to establish the corresponding two databases XA (javax.sql.XADataSource) types of data sources.
2 to ensure distributed transaction using the UserTransaction.
try{
Connection conn1 = datasource1.getConnection();
Connection conn2 = datasource2.getConnection();
UserTransaction ut = getUserTransaction();
ut.begin();
// ???? // ????
ut.commit(); // ????????????
} catch(SQLException ex) {
ut.rollback(); // ??????????????
} finally {
conn.close();
} ??
????UserTransaction??????????
UserTransaction tx = (UserTransaction)
ctx.lookup("jndi/UserTransaction");
J2EE developers use the Data Access Object (DAO) design pattern to the underlying data access logic and high-level business logic. Implement the DAO pattern can be more focused on writing data access code. This article, Java developer Sean C. Sullivan DAO programming from three aspects discussed structural features: Transaction division, exception handling, logging.
in the last 18 months, I and a good software development team to work together to develop customized WEB-based supply chain management applications program . Our application to access a wide range of data persistence layer, including shipping status, supply chain systems, inventory, shipment, project management data, and user attributes, etc. We use the JDBC API to connect our company a variety of database platforms, and in the entire application DAO design pattern is applied through the entire application process application data access object (DAO) design pattern allows us to put the underlying data access logic and business logic separate upper and we for each data source created to provide CRUD (create, read, update, delete) operations of the DAO classes. In this article, I will introduce you to the DAO implementation strategy and create a better DAO class technology and I will clear introduction of logging, exception handling, and transaction demarcation three techniques you will learn in your DAO classes how these three technologies together. This article assumes that you are familiar with JDBC API, SQL and relational database programming .
we first look at the DAO design pattern and data access objects.
DAO foundation
DAO mode is the standard J2EE design patterns. Developers use this model to the underlying data access operations and business logic separate from the upper A typical DAO implementation has the following several components:
1. a DAO factory class;
2. a DAO interface;
3. a specific DAO interface implementation class;
4. data transfer objects (sometimes called value objects).
specific DAO class contains data from a specific data source access logic. In the following you will learn during the design and implementation of data access object technology.
transaction demarcation:
About DAO to remember one important thing is that they are transactional objects. Each operation performed by DAO (like create, update, or delete data) and transactions are associated. Similarly, the transaction demarcation (transaction demarcation) concept is particularly important.
transaction demarcation is properly defined in the affairs of the way. J2EE specification describes the transaction divided in two modes: Programmed Affairs (programmatic) and declarative transactions (declarative). The following table is a split of these two modes:
declarative transaction demarcation | programmatic transaction demarcation |
programmers use EJB deployment descriptor declarative transaction attribute | programmers to assume the responsibility of writing the business logic code. |
runtime environment (EJB container) use these attributes to automatically manage transactions. | application through an API interface to control transaction. |
I will focus on the programmatic transaction demarcation.
the same as in the previous description, DAOs are some of the transaction object. A typical DAO to perform like create, update, and delete transactional operations. In designing a DAO, the first question to ask yourself the following questions:
1, the transaction will how to start?
2, how will the end of the transaction?
3, that object will assume the responsibility of starting a transaction?
4, that object will assume the responsibility of the end of a transaction?
5, DAO start and end a transaction should bear the responsibility?
6, the application needs to access multiple cross DAO do?
7, a transaction consists of one or multiple DAO DAO?
8, one of the DAO DAO contains other way to do this?
Answering these questions will help you choose the best for the DAO object transaction demarcation strategy. The transaction demarcation to ADO There are two main strategies. One way is to use DAO assume responsibility for transaction demarcation; another kind is the extension of the transaction, which the transaction demarcation to the calling method DAO objects. If you choose the former, you will want to embed in the DAO class transaction code. If you choose the latter, the transaction code will be written on the outside of the DAO classes. We will use the simple code examples to better understand how these two methods work.
1 shows an example with two data operations DAO: create (create) and update (update):
public void createWarehouseProfile(WHProfile profile)?
|
Example 2 shows a simple transaction, transaction demarcation code is in the DAO classes outside. Note: In this example the caller DOA operations into a plurality of the transaction.
tx.begin()? // start the transaction |
transaction demarcation strategy for such services in a single transaction to access multiple applications DAO is particularly important.
you can use the JDBC API can also use the Java Transaction API (JTA) to achieve affairs division. JDBC transaction demarcation than JTA transaction demarcation simple, but JTA provides better flexibility. In the following period, we will see a further transaction demarcation mechanism.
using JDBC transaction demarcation
JDBC transactions are controlled using the Connection object. JDBC connection interface (java.sql.Connection) provides two transaction modes: automatic and manual submission submission. Java.sql.Connection to control the affairs of the following methods:
.public void setAutoCommit(Boolean)
|
Example 3 shows how to use the JDBC API to demarcate transactions:
import java.sql.*?
|
using JDBC transaction demarcation, you can put multiple SQL statements into a single transaction. One disadvantage is that JDBC transaction transaction scope is limited to a single database connection. A JDBC transaction can not span multiple databases. Next, we will see how to use the JTA transaction demarcation to do. Because unlike JDBC JTA as is widely understood, so I first summarize a lower JTA.
JTA overview
Java Transaction API (JTA; Java Transaction API) and its fellow Java Transaction Service (JTS; Java Transaction Service), for the J2EE platform provides a distributed transaction services. A distributed transaction (distributed transaction) includes a transaction manager (transaction manager) and one or more resource managers (resource manager). A resource manager (resource manager) is any type of persistent data storage. Transaction Manager (transaction manager) bear all transaction participants communicate with each unit's responsibility. Next station shows the transaction manager and resource management relationships.
JTA transaction more powerful than JDBC transactions. A JTA transaction can have multiple participants, and a JDBC transactions were limited to a single database connection. Any of the following components of the Java platform can participate in a JTA transaction:
. JDBC Connection
. JDO PersistenceManager object
. JMS queue
. JMS topic
. enterprise JavaBeans (EJB)
. one with J2EE Connector Architecture specification compiled resource allocator.
using JTA transaction demarcation
use JTA to divide a transaction, the application calls javax.transaction.UserTransaction interface methods. Example 4 shows a typical search UseTransaction the JNDI object.
import javax.transaction.*?
|
application has UserTransaction object reference, you can like to start the transaction as Example 5.
utx.begin()?
|
When the application calls commit (), the transaction manager uses two-phase commit protocol to end the transaction. JTA transaction control methods:
. javax.transaction.UserTransaction transaction control interface provides the following methods:
.public void begin()
|
application calls the begin () to start the transaction, you can call the commit () can also call rollback () to end the transaction.
use JTA and JDBC
developers often use JDBC DAO class as the underlying data manipulation. If you plan to use JTA to demarcate transactions, you will need one to achieve a javax.sql.XADataSource, javax.sql.XAConnection and javax.sql.XAResource Interface for JDBC Drivers . The driver implements these interfaces will be able to participate in JTA transactions. XAConnection a XADataSource object is an object factory. XAConnections is to participate in JTA transactions connection.
you need to use the application server management tools to create XADataSource object. For special instructions please refer to the application server documentation and the JDBC driver documentation.
J2EE applications use JNDI to look up the data source. Once the application has a data source object references, which will call javax.sql.DataSource.getConnection () to get the connection to the database.
XA connection is different from non-XA connections. Remember that the XA connection is a JTA transaction participants. This means that XA JDBC connection does not support auto-commit feature. That application does not have to call on XA connection java.sql.Connection.commit () or java.sql.Connection.rollback (). Instead, the application should use UserTransaction.begin (), UserTransaction.commit () and UserTransaction.rollback ().
choose the best method
We have already discussed how the division of JDBC and JTA transactions. Each method has its advantages, back to this you need to decide for your application to select a most suitable approach. In our team for many recent transaction demarcation project uses the JDBC API to create a DAO class. This DAO classes are summarized as follows:
. transaction demarcation code is embedded into the DAO classes inside
. DAO classes using the JDBC API transaction demarcation
. caller does not demarcate transaction method
. transaction scope is limited to a single JDBC Connection
JDBC transaction for complex enterprise applications are not always effective. If your transaction will span multiple DAO objects or multiple databases, then the following implementation strategy may be more appropriate:
. divide the transaction with JTA
. separate transaction demarcation code is DAO
. demarcate transactions caller assume responsibility
. DAO participating in a global transaction
JDBC method due to its simplicity and attractive, JTA method provides more flexibility. You choose what kind of implementation will depend on your application's specific needs.
logging and DAO
a good DAO implementation class will use logging to capture information about it in the run-time behavior details. You can choose to log exceptions, configuration information, connection status, JDBC driver metadata or query parameters. Logs on the development of the entire period are beneficial. I often check the application during development, testing and products during logging.
In this paragraph, we will show how to Jakarta Commaons Logging section of the combination of a DAO examples. Before we get started, let's review some basics.
Select a log case library
Many developers use the basic log form: System.out.println and System.err.println.Println statements. This form of fast and convenient, but they do not provide a complete log system capabilities. The following table lists the Java platform logging library:
log library | open it? | URL |
Java.util.logging | No | http://java.sun.com/j2ee < / p> |
Jakarta Log4j | is | http://hajarta.apache.org/log4j/ |
Jakarta Commons Logging | is | http:/Jakarta.apache.org/commons/logging.html |
Java.util.logging J2SE1.4 platform is a standard API. However, most developers agree that Jakarta Log4j provides greater functionality and flexibility. One of the advantages beyond java.util.logging Log4j is that it supports J2SE1.3 and J2SE1.4 platform.
Jakarta Commons Logging can be used and java.util.loggin or Jakarta Log4j work together. Commons Logging is a treat for your application independent abstraction layer logging implementations. You can use the Commons Logging by changing a configuration file with the following log implementations to exchange data. Commons Logging is used JAKARTA Struts1.1 and Jakarta HttpClient2.0 in.
a log Example Example 7 shows how to use a class DOA Jakarta Commons Logging
import org.apache.commons.logging.*?
|
log is to assess the application of the basic parts. If you are having a DAO fails, the log will often mistake for understanding what happens to provide the best possible information. Combine the log into your DAO, make sure to get debug and effective means to solve the problem.
DAO exception handling in
we've looked at transaction demarcation and logging, and how they are now used for data access objects have an in-depth understanding. Our third and final exception handling is to be discussed. Here are some simple guidelines to use exception handling your DAO easier to use, more robust and more maintainable.
in achieving DAO mode, the following questions to test filter:
. the DAO's public interface methods will throw checked exceptions do?
. If yes, what will throw checked exceptions?
. DAO implementation class in how to handle exceptions like.
in the process of working with the DAO pattern, our team has developed a set of exception handling policy. Following these guidelines will have a significant degree of improvement in your DAO:
. DAO method should throw a meaningful exception.
. DAO methods should not throw java.lang.Exception exception. Because java.lang.Exception too generic, it can not contain all the information about potential problems.
. DAO method should not throw an exception java.sql.SQLException. Underlying JDBC SQLException is an exception, DAO application efforts Package JDBC exceptions JDBC exceptions should not be left to other parts of the application.
. the DAO interface methods should only deal with the expectations of the caller throw checked exceptions. If the caller can not use the appropriate method to handle exceptions thrown test filter does not check the resistance (run-time run-time) exception. If your data access code catches an exception, do not want to ignore it. Ignore catch the exception of the DAO is processed.
. use exception chain to the bottom of a high-level exception is passed to the processor.
. define a standard test filter DAO exception classes. Spring framework provides an excellent predefined DAO exception classes.
see Resources, there are exceptions and exception handling view more detailed information technologies.
implementation example: MovieDAO
MoveDAO is a demonstration of the discussion in this article are all technologies, including transaction demarcation, logging and exception handling. In the Resources section you will find MovieDAO source code. It is of the following three packages:
.daoexamples.exception
|
the DAO pattern achieved by the following classes and interfaces:
.daoexamples.movie.MovieDAOFactory
|
MovieDAO interface defines the DAO data manipulation.
没有评论:
发表评论