Heim > Java > javaLernprogramm > Detaillierte Erläuterung der Transaktionsverwaltungsmethoden im Spring-Framework von Java

Detaillierte Erläuterung der Transaktionsverwaltungsmethoden im Spring-Framework von Java

高洛峰
Freigeben: 2017-01-23 11:06:09
Original
1248 Leute haben es durchsucht

Eine Datenbanktransaktion ist eine Abfolge von Vorgängen, die als eine einzelne Arbeitseinheit behandelt werden. Diese Vorgänge sind entweder alle abgeschlossen oder alle sind nicht erfolgreich. Das Transaktionsmanagement ist ein wichtiger Bestandteil von RDBMS für Unternehmensanwendungen, um Datenintegrität und -konsistenz sicherzustellen. Das Konzept einer Transaktion kann durch die folgenden vier Schlüsseleigenschaften von ACID beschrieben werden:

Atomizität: Eine Transaktion sollte als eine einzelne Operationseinheit behandelt werden, die jede gesamte Abfolge von Operationen darstellt, die erfolgreich oder nicht erfolgreich sind.

Konsistenz: Dies stellt die referenzielle Integrität der Datenbank, die Konsistenz eindeutiger Primärschlüssel in Tabellen usw. dar.

Isolation: Es können viele Transaktionen gleichzeitig denselben Datensatz verarbeiten. Jede Transaktion sollte von anderen isoliert werden, um Datenbeschädigung zu verhindern.

Persistenz: Sobald eine Transaktion abgeschlossen ist, müssen die Ergebnisse dieser Transaktion dauerhaft gemacht werden und können aufgrund eines Systemausfalls nicht aus der Datenbank gelöscht werden.

Ein echtes RDBMS-Datenbanksystem garantiert alle vier Attribute für jede Transaktion. Eine einfache Ansicht zum Ausgeben einer Transaktion mithilfe einer SQL-Datenbank ist wie folgt:

Verwenden Sie den Befehl „Begin Transaction“, um eine Transaktion zu starten.

Führen Sie mithilfe von SQL-Abfragen verschiedene Lösch-, Aktualisierungs- oder Einfügevorgänge aus.

Wenn alle Vorgänge erfolgreich sind, führen Sie einen Commit durch, andernfalls setzen Sie alle Vorgänge zurück.

Eine Abstraktionsschicht über den verschiedenen zugrunde liegenden Transaktionsverwaltungs-APIs, die vom Spring-Framework bereitgestellt werden. Die Transaktionsunterstützung im Frühjahr zielt darauf ab, einen Ersatz für EJB-Transaktionen bereitzustellen, indem POJOs Transaktionsfunktionen hinzugefügt werden. Spring unterstützt sowohl programmatisches als auch deklaratives Transaktionsmanagement. Ein EJB-Anwendungsserver ist erforderlich, die Spring-Transaktionsverwaltung wird jedoch ohne die Notwendigkeit eines Anwendungsservers implementiert.

Lokale und globale Transaktionen
Lokale Transaktionen gelten für eine einzelne Transaktionsressource wie eine JDBC-Verbindung, während globale Transaktionen verteilte Systeme wie mehrere Transaktionsressourcen umfassen können.

Lokale Transaktionsverwaltung kann in einer zentralisierten Computerumgebung nützlich sein, in der sich die Komponenten und Ressourcen einer Anwendung an einem einzigen Standort befinden, während die Transaktionsverwaltung nur die lokale Datenverwaltung umfasst, die auf einem separaten Computer ausgeführt wird. Lokale Transaktionen sind einfacher zu implementieren.

Globales Transaktionsmanagement ist in einer verteilten Computerumgebung erforderlich, in der alle Ressourcen auf mehrere Systeme verteilt sind. In diesem Fall muss das Transaktionsmanagement sowohl auf lokaler als auch auf globaler Ebene erfolgen. Eine verteilte oder globale Transaktion wird auf mehreren Systemen ausgeführt und ihre Ausführung erfordert eine Koordination zwischen dem globalen Transaktionsverwaltungssystem und allen lokalen Datenmanagern aller zugehörigen Systeme.

Programmierung und Deklaration
Spring unterstützt zwei Arten der Transaktionsverwaltung:

Programmatische Transaktionsverwaltung: Spring unterstützt zwei Arten der Transaktionsverwaltung:

Deklarative Transaktionsverwaltung: Dies bedeutet Die Transaktionsverwaltung ist von Ihrem Geschäftscode getrennt. Zur Verwaltung von Transaktionen nutzen Sie ausschließlich Annotationen oder XML-basierte Konfigurationen.

Programmatic Transaction Management
Programmatic Transaction Management ermöglicht Ihnen die Verwaltung von Transaktionen mithilfe von Programmatic-Quellcode. Dies bietet große Flexibilität, ist jedoch schwierig aufrechtzuerhalten.

Bevor wir beginnen, handelt es sich um mindestens zwei Datenbanktabellen, auf denen wir mithilfe von Transaktionen verschiedene CRUD-Operationen durchführen können. Nehmen wir die Schülertabelle, die in einer MySQL-Datenbank zum Testen mit der folgenden DDL erstellt werden kann:

CREATE TABLE Student(
  ID  INT NOT NULL AUTO_INCREMENT,
  NAME VARCHAR(20) NOT NULL,
  AGE INT NOT NULL,
  PRIMARY KEY (ID)
);
Nach dem Login kopieren
Nach dem Login kopieren

Die zweite Tabelle enthält die Noten, anhand derer wir die Schüler benoten Jahre. Hier ist SID der Fremdschlüssel der Tabelle.

CREATE TABLE Marks(
  SID INT NOT NULL,
  MARKS INT NOT NULL,
  YEAR  INT NOT NULL
);
Nach dem Login kopieren
Nach dem Login kopieren

Lassen Sie uns PlatformTransactionManager verwenden, um Programmiermethoden zur Implementierung von Transaktionen direkt zu implementieren. Um eine neue Transaktion zu starten, muss eine Instanz von TransactionDefinition mit den entsprechenden Transaktionsattributen vorhanden sein. In diesem Beispiel erstellen wir einfach eine Instanz von DefaultTransactionDefinition unter Verwendung der Standardtransaktionseigenschaften.

Sobald eine TransactionDefinition erstellt wurde, können Sie eine Transaktion starten, indem Sie die Methode getTransaction() aufrufen, die eine Instanz des TransactionStatus-Objekts zurückgibt. Das TransactionStatus-Objekt hilft dabei, den aktuellen Status der Transaktion zu verfolgen, und schließlich können Sie, wenn alles gut geht, die Methode commit(PlatformTransactionManager) verwenden, um die Transaktion festzuschreiben, andernfalls können Sie rollback() verwenden, um den abgeschlossenen Vorgang rückgängig zu machen.

Jetzt schreiben wir eine Spring JDBC-Anwendung, die einfache Vorgänge für die Tabellen „Student“ und „Marks“ implementiert.
Das Folgende ist der Inhalt der Datenzugriffsobjektschnittstellendatei StudentDAO.java:

package com.yiibai;
 
import java.util.List;
import javax.sql.DataSource;
 
public interface StudentDAO {
  /**
  * This is the method to be used to initialize
  * database resources ie. connection.
  */
  public void setDataSource(DataSource ds);
  /**
  * This is the method to be used to create
  * a record in the Student and Marks tables.
  */
  public void create(String name, Integer age, Integer marks, Integer year);
  /**
  * This is the method to be used to list down
  * all the records from the Student and Marks tables.
  */
  public List<StudentMarks> listStudents();
}
Nach dem Login kopieren
Nach dem Login kopieren


Das Folgende ist der Inhalt der StudentMarks.java-Datei:

package com.yiibai;
 
public class StudentMarks {
  private Integer age;
  private String name;
  private Integer id;
  private Integer marks;
  private Integer year;
  private Integer sid;
 
  public void setAge(Integer age) {
   this.age = age;
  }
  public Integer getAge() {
   return age;
  }
 
  public void setName(String name) {
   this.name = name;
  }
  public String getName() {
   return name;
  }
 
  public void setId(Integer id) {
   this.id = id;
  }
  public Integer getId() {
   return id;
  }
  public void setMarks(Integer marks) {
   this.marks = marks;
  }
  public Integer getMarks() {
   return marks;
  }
 
  public void setYear(Integer year) {
   this.year = year;
  }
  public Integer getYear() {
   return year;
  }
 
  public void setSid(Integer sid) {
   this.sid = sid;
  }
  public Integer getSid() {
   return sid;
  }
}
Nach dem Login kopieren
Nach dem Login kopieren

Das Folgende ist der Inhalt der StudentMarksMapper.java-Datei:

package com.yiibai;
 
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;
 
public class StudentMarksMapper implements RowMapper<StudentMarks> {
  public StudentMarks mapRow(ResultSet rs, int rowNum) throws SQLException {
 
   StudentMarks studentMarks = new StudentMarks();
 
   studentMarks.setId(rs.getInt("id"));
   studentMarks.setName(rs.getString("name"));
   studentMarks.setAge(rs.getInt("age"));
   studentMarks.setSid(rs.getInt("sid"));
   studentMarks.setMarks(rs.getInt("marks"));
   studentMarks.setYear(rs.getInt("year"));
 
   return studentMarks;
  }
}
Nach dem Login kopieren
Nach dem Login kopieren


Das Folgende ist die Implementierungsklassendatei StudentJDBCTemplate.java, die die DAO-Schnittstelle StudentDAO definiert:

package com.yiibai;
 
import java.util.List;
import javax.sql.DataSource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
 
public class StudentJDBCTemplate implements StudentDAO {
  private DataSource dataSource;
  private JdbcTemplate jdbcTemplateObject;
  private PlatformTransactionManager transactionManager;
 
  public void setDataSource(DataSource dataSource) {
   this.dataSource = dataSource;
   this.jdbcTemplateObject = new JdbcTemplate(dataSource);
  }
 
  public void setTransactionManager(
   PlatformTransactionManager transactionManager) {
   this.transactionManager = transactionManager;
  }
 
  public void create(String name, Integer age, Integer marks, Integer year){
 
   TransactionDefinition def = new DefaultTransactionDefinition();
   TransactionStatus status = transactionManager.getTransaction(def);
 
   try {
     String SQL1 = "insert into Student (name, age) values (?, ?)";
     jdbcTemplateObject.update( SQL1, name, age);
 
     // Get the latest student id to be used in Marks table
     String SQL2 = "select max(id) from Student";
     int sid = jdbcTemplateObject.queryForInt( SQL2 );
 
     String SQL3 = "insert into Marks(sid, marks, year) " +
            "values (?, ?, ?)";
     jdbcTemplateObject.update( SQL3, sid, marks, year);
 
     System.out.println("Created Name = " + name + ", Age = " + age);
     transactionManager.commit(status);
   } catch (DataAccessException e) {
     System.out.println("Error in creating record, rolling back");
     transactionManager.rollback(status);
     throw e;
   }
   return;
  }
 
  public List<StudentMarks> listStudents() {
   String SQL = "select * from Student, Marks where Student.id=Marks.sid";
 
   List <StudentMarks> studentMarks = jdbcTemplateObject.query(SQL,
                     new StudentMarksMapper());
   return studentMarks;
  }
}
Nach dem Login kopieren


Jetzt verschieben wir die Hauptanwendungsdatei MainApp.java, die wie folgt lautet:

package com.yiibai;
import java.util.List;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.yiibai.StudentJDBCTemplate;
 
public class MainApp {
  public static void main(String[] args) {
   ApplicationContext context =
       new ClassPathXmlApplicationContext("Beans.xml");
 
   StudentJDBCTemplate studentJDBCTemplate =
   (StudentJDBCTemplate)context.getBean("studentJDBCTemplate");
    
   System.out.println("------Records creation--------" );
   studentJDBCTemplate.create("Zara", 11, 99, 2010);
   studentJDBCTemplate.create("Nuha", 20, 97, 2010);
   studentJDBCTemplate.create("Ayan", 25, 100, 2011);
 
   System.out.println("------Listing all the records--------" );
   List<StudentMarks> studentMarks = studentJDBCTemplate.listStudents();
   for (StudentMarks record : studentMarks) {
     System.out.print("ID : " + record.getId() );
     System.out.print(", Name : " + record.getName() );
     System.out.print(", Marks : " + record.getMarks());
     System.out.print(", Year : " + record.getYear());
     System.out.println(", Age : " + record.getAge());
   }
  }
}
Nach dem Login kopieren

Das Folgende ist die Konfigurationsdatei Beans.xml-Datei:

<?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 ">
 
  <!-- Initialization for data source -->
  <bean id="dataSource"
   class="org.springframework.jdbc.datasource.DriverManagerDataSource">
   <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
   <property name="url" value="jdbc:mysql://localhost:3306/TEST"/>
   <property name="username" value="root"/>
   <property name="password" value="password"/>
  </bean>
 
  <!-- Initialization for TransactionManager -->
  <bean id="transactionManager"
   class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
   <property name="dataSource" ref="dataSource" /> 
  </bean>
 
  <!-- Definition for studentJDBCTemplate bean -->
  <bean id="studentJDBCTemplate"
   class="com.yiibai.StudentJDBCTemplate">
   <property name="dataSource" ref="dataSource" />
   <property name="transactionManager" ref="transactionManager" /> 
  </bean>
    
</beans>
Nach dem Login kopieren

Erstellen Sie den Quellcode und führen Sie die Anwendung aus, sobald die Bean-Konfigurationsdatei fertig ist. Wenn alles gut geht, wird Folgendes gedruckt:

------Records creation--------
Created Name = Zara, Age = 11
Created Name = Nuha, Age = 20
Created Name = Ayan, Age = 25
------Listing all the records--------
ID : 1, Name : Zara, Marks : 99, Year : 2010, Age : 11
ID : 2, Name : Nuha, Marks : 97, Year : 2010, Age : 20
ID : 3, Name : Ayan, Marks : 100, Year : 2011, Age : 25
Nach dem Login kopieren

Deklaratives Transaktionsmanagement
Der Ansatz des deklarativen Transaktionsmanagements hilft Ihnen bei der Verwaltung der Konfiguration statt im Quellcode fest codierter Transaktionen. Dies bedeutet, dass die Transaktionsverwaltung getrennt vom Geschäftscode erfolgen kann. Verwalten Sie Transaktionen nur mithilfe von Anmerkungen oder XML-basierter Konfiguration. Die Konfiguration der Bean gibt an, dass die Methode transaktional ist. Hier sind die deklarativen und transaktionsbezogenen Schritte:

我们使用标签,这将创建我们定义了一个切入点匹配所有我们想做成事务,并引用其中的事务通知方法的事务并同时处理建议。

如果一个方法的名字已被列入事务配置,然后创建意见,将调用该方法之前开始交易。

目标方法将在一个try/ catch块被执行。

如果方法正常完成,AOP的建议提交事务成功,否则执行回滚。

让我们来看看为何上述步骤的工作,但在我们开始之前,它至少有两个数据库表上,我们可以用交易的帮助下执行各种CRUD操作是很重要的。让我们以Student表,它可以在MySQL数据库中测试用下面的DDL创建:

CREATE TABLE Student(
  ID  INT NOT NULL AUTO_INCREMENT,
  NAME VARCHAR(20) NOT NULL,
  AGE INT NOT NULL,
  PRIMARY KEY (ID)
);
Nach dem Login kopieren
Nach dem Login kopieren

第二个表是Marks ,我们将保持标记为基于多年的学生。这里SID是表Student的外键。

CREATE TABLE Marks(
  SID INT NOT NULL,
  MARKS INT NOT NULL,
  YEAR  INT NOT NULL
);
Nach dem Login kopieren
Nach dem Login kopieren

同样来看一下相照应的例子。
以下是数据访问对象接口文件StudentDAO.java的内容:

package com.yiibai;
 
import java.util.List;
import javax.sql.DataSource;
 
public interface StudentDAO {
  /**
  * This is the method to be used to initialize
  * database resources ie. connection.
  */
  public void setDataSource(DataSource ds);
  /**
  * This is the method to be used to create
  * a record in the Student and Marks tables.
  */
  public void create(String name, Integer age, Integer marks, Integer year);
  /**
  * This is the method to be used to list down
  * all the records from the Student and Marks tables.
  */
  public List<StudentMarks> listStudents();
}
Nach dem Login kopieren
Nach dem Login kopieren

以下是StudentMarks.java文件的内容:

package com.yiibai;
 
public class StudentMarks {
  private Integer age;
  private String name;
  private Integer id;
  private Integer marks;
  private Integer year;
  private Integer sid;
 
  public void setAge(Integer age) {
   this.age = age;
  }
  public Integer getAge() {
   return age;
  }
 
  public void setName(String name) {
   this.name = name;
  }
  public String getName() {
   return name;
  }
 
  public void setId(Integer id) {
   this.id = id;
  }
  public Integer getId() {
   return id;
  }
  public void setMarks(Integer marks) {
   this.marks = marks;
  }
  public Integer getMarks() {
   return marks;
  }
 
  public void setYear(Integer year) {
   this.year = year;
  }
  public Integer getYear() {
   return year;
  }
 
  public void setSid(Integer sid) {
   this.sid = sid;
  }
  public Integer getSid() {
   return sid;
  }
}
Nach dem Login kopieren
Nach dem Login kopieren


以下是StudentMarksMapper.java文件的内容:

package com.yiibai;
 
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;
 
public class StudentMarksMapper implements RowMapper<StudentMarks> {
  public StudentMarks mapRow(ResultSet rs, int rowNum) throws SQLException {
 
   StudentMarks studentMarks = new StudentMarks();
 
   studentMarks.setId(rs.getInt("id"));
   studentMarks.setName(rs.getString("name"));
   studentMarks.setAge(rs.getInt("age"));
   studentMarks.setSid(rs.getInt("sid"));
   studentMarks.setMarks(rs.getInt("marks"));
   studentMarks.setYear(rs.getInt("year"));
 
   return studentMarks;
  }
}
Nach dem Login kopieren
Nach dem Login kopieren


下面是实现类文件StudentJDBCTemplate.java 的定义DAO接口StudentDAO:

package com.yiibai;
 
import java.util.List;
import javax.sql.DataSource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
 
public class StudentJDBCTemplate implements StudentDAO{
  private JdbcTemplate jdbcTemplateObject;
 
  public void setDataSource(DataSource dataSource) {
   this.jdbcTemplateObject = new JdbcTemplate(dataSource);
  }
 
  public void create(String name, Integer age, Integer marks, Integer year){
 
   try {
     String SQL1 = "insert into Student (name, age) values (?, ?)";
     jdbcTemplateObject.update( SQL1, name, age);
 
     // Get the latest student id to be used in Marks table
     String SQL2 = "select max(id) from Student";
     int sid = jdbcTemplateObject.queryForInt( SQL2 );
 
     String SQL3 = "insert into Marks(sid, marks, year) " +
            "values (?, ?, ?)";
     jdbcTemplateObject.update( SQL3, sid, marks, year);
 
     System.out.println("Created Name = " + name + ", Age = " + age);
     // to simulate the exception.
     throw new RuntimeException("simulate Error condition") ;
   } catch (DataAccessException e) {
     System.out.println("Error in creating record, rolling back");
     throw e;
   }
  }
 
  public List<StudentMarks> listStudents() {
   String SQL = "select * from Student, Marks where Student.id=Marks.sid";
 
   List <StudentMarks> studentMarks=jdbcTemplateObject.query(SQL,
   new StudentMarksMapper());
   return studentMarks;
  }
}
Nach dem Login kopieren


现在我们移动主应用程序文件MainApp.java,如下:

package com.yiibai;
import java.util.List;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
 
public class MainApp {
  public static void main(String[] args) {
   ApplicationContext context =
       new ClassPathXmlApplicationContext("Beans.xml");
 
   StudentDAO studentJDBCTemplate =
   (StudentDAO)context.getBean("studentJDBCTemplate");
    
   System.out.println("------Records creation--------" );
   studentJDBCTemplate.create("Zara", 11, 99, 2010);
   studentJDBCTemplate.create("Nuha", 20, 97, 2010);
   studentJDBCTemplate.create("Ayan", 25, 100, 2011);
 
   System.out.println("------Listing all the records--------" );
   List<StudentMarks> studentMarks = studentJDBCTemplate.listStudents();
   for (StudentMarks record : studentMarks) {
     System.out.print("ID : " + record.getId() );
     System.out.print(", Name : " + record.getName() );
     System.out.print(", Marks : " + record.getMarks());
     System.out.print(", Year : " + record.getYear());
     System.out.println(", Age : " + record.getAge());
   }
  }
}
Nach dem Login kopieren


以下是配置文件beans.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"
  xmlns:tx="http://www.springframework.org/schema/tx"
  xmlns:aop="http://www.springframework.org/schema/aop"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/tx
  http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
  http://www.springframework.org/schema/aop
  http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
 
  <!-- Initialization for data source -->
  <bean id="dataSource"
   class="org.springframework.jdbc.datasource.DriverManagerDataSource">
   <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
   <property name="url" value="jdbc:mysql://localhost:3306/TEST"/>
   <property name="username" value="root"/>
   <property name="password" value="cohondob"/>
  </bean>
  
  <tx:advice id="txAdvice" transaction-manager="transactionManager">
   <tx:attributes>
   <tx:method name="create"/>
   </tx:attributes>
  </tx:advice>
  
  <aop:config>
   <aop:pointcut id="createOperation"
   expression="execution(* com.yiibai.StudentJDBCTemplate.create(..))"/>
   <aop:advisor advice-ref="txAdvice" pointcut-ref="createOperation"/>
  </aop:config>
  
  <!-- Initialization for TransactionManager -->
  <bean id="transactionManager"
  class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
   <property name="dataSource" ref="dataSource" /> 
  </bean>
 
  <!-- Definition for studentJDBCTemplate bean -->
  <bean id="studentJDBCTemplate"
  class="com.yiibai.StudentJDBCTemplate">
   <property name="dataSource" ref="dataSource" />
  </bean>
 
</beans>
Nach dem Login kopieren

创建源代码和bean配置文件来完成,让我们运行应用程序。如果一切顺利,这将打印以下,将引发异常。在这种情况下,事务将回滚,并没有记录将在数据库表中创建。

------Records creation--------
Created Name = Zara, Age = 11
Exception in thread "main" java.lang.RuntimeException: simulate Error condition
Nach dem Login kopieren

你可以试试上面的例子中去除异常后,在这种情况下,应该提交事务,应该看到在数据库中的记录。

更多详解Java的Spring框架中的事务管理方式相关文章请关注PHP中文网!

Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage