Prefacio
JDBC Templet de Spring es una encapsulación básica que Spring usa para JDBC. Principalmente ayuda a los programadores a administrar conexiones de bases de datos, y el resto de los métodos de uso no son una gran diferencia al usar JDBC directamente.
Requisitos comerciales
Todos están familiarizados con el uso de JDBC. Esto es principalmente para demostrar los pasos para usar JDBC Templet Spring en Springboot, por lo que diseñamos un requisito simple. Una operación de cuajada de un objeto de usuario. Un objeto tiene dos propiedades, una es ID y el otro es el nombre. Almacenado en la tabla Auth_user en MySQL.
Crear nuevos proyectos y agregar dependencias
Cree un proyecto SpringBoot vacío en la idea IntelliJ. Referencia de pasos específicos
Tutorial gráfico de IntelliJ Idea para crear proyectos de bola de primavera. De acuerdo con los requisitos de este ejemplo, necesitamos agregar las siguientes tres dependencias
<Spendency> <MoupRoupId> org.springframework.boot </groupid> <artifactid> spring-boot-starter-web </artifactid> </peperspency> <paperency> <uproupid> org.springframework.boot </groupId> <artifactid> spring-boot-starter-jdbc </artifactid> <//dependency> </dependency> <MoupRid> mysql </proupid> <artifactId> mysql-confonnector-java </arfactid> <versión> 6.0.6 </versión> </pendency>
Debido a que queremos publicar el servicio HTTP REST, agregamos la dependencia de Spring-Boot-Starter-Web. Aquí queremos usar el método JDBC Tempet para acceder a la base de datos, por lo que agregamos la dependencia de Spring-Boot-Starter-JDBC para acceder a la base de datos MySQL, por lo que agregamos la última versión del controlador JDBC de MySQL.
Preparar el entorno de la base de datos
Suponga que MySQL 5.7 ya está instalado en el sistema operativo Linux. Las siguientes operaciones se ejecutan en la línea de comandos del sistema operativo, iniciado sesión en el cliente de línea de comandos de MySQL a través del usuario root.
Construir una base de datos y una tabla
Crear base de datos SpringBoot_jdbc; Crear tabla Auth_user (UUID BigInt no NULL, Name Varchar (32), Key Primario (UUID)) Charset predeterminado = UTF8MB4;
Establecer permisos de usuario
otorgue todos los privilegios en springboot_jdbc.* a 'springboot'@'%' identificado por 'springboot'; flush privilegios;
Configurar la fuente de datos (grupo de conexión)
La fuente de datos de SpringBoot se configura automáticamente. En SpringBoot 2.0, hay varias configuraciones de fuente de datos disponibles, y eligen qué fuente de datos usar realmente en el último orden de Hikaricp -> Tomcat Pooling -> Commons DBCP2.
Cuando el proyecto agrega la dependencia de Spring-Boot-Starter-JDBC, la dependencia de la fuente de datos HIKARICP ya está incluida, por lo que la fuente de datos del grupo de conexión HIKARICP se configura automáticamente aquí.
Agregue la siguiente configuración en AppLications.Properties
#Configuración de la fuente de datos General spring.datasource.driver-class-name = com.mysql.cj.jdbc.driverspring.datasource.url = jdbc: mysql: //10.110.2.5: 3306/spri ng-boot-jdbc? charset = utf8mb4 & Usessl = falsspring.datasource.username = springbootspring.datasource.password = springboot# Hikari Fuente de datos Configuración específica Spring.datasource.hikari.maximum-pool-size = 20Spring.datasource.hikari.minimum-idle = 5
Entre ellos, la mayoría de las configuraciones de la fuente de datos de Hikari se muestran en la figura a continuación. Puede verificar el significado de cada configuración
Desarrollo de programas
Entidad de la base de datos de usuarios
Según los requisitos, la entidad de datos del usuario correspondiente tiene dos atributos, uno es ID y el otro es el nombre. Este es un objeto POJO puro.
Paquete com.yanggaochao.springboot.learn.springbootjdbclearn.domain.dao;/*** Objeto de entidad de usuario** @author yang gaochao* @since 2018-03-09*/public class Userdo {ID de larga privada; nombre de cadena privada; public Long getId () {return id; } public void setid (ID long) {this.id = id; } public String getName () {nombre de retorno; } public void setName (nombre de cadena) {this.name = name; }} Objeto general de retorno de descanso HTTP
Por lo general, en la interfaz HTTP REST, no solo queremos devolver directamente el contenido del objeto comercial, sino que también devuelve cierta información común, como el resultado de la llamada de interfaz, el mensaje de texto personalizado devuelto cuando la llamada falla, etc. Entonces necesitamos establecer dos objetos de retorno comunes, además de devolver los resultados de la llamada de la interfaz común y los mensajes de texto, uno incluye un contenido comercial y uno contiene una colección que contiene múltiples contenido comercial. La definición específica es la siguiente
Return Object para contenido comercial separado
paquete com.yanggaochao.springboot.learn.springbootjdbclearn.domain.bo;/*** El objeto único devuelve el resultado** @author yang gaochao* @since 2018-03-09*/clase pública restitemresult <t> {Resultado privado de cadena; mensaje de cadena privada; Artículo T Privado T; public String getResult () {return resultado; } public void setResult (resultado de cadena) {this.result = resultado; } public String getMessage () {Mensaje de retorno; } public void setMessage (mensaje de cadena) {this.message = mensaje; } public t getItem () {return item; } public void setItem (t item) {this.item = item; }} COLECCIÓN DEL CONTENIDO NEGOCIO DEVOLUCIONES OBJETIVO
paquete com.yanggaochao.springboot.learn.springbootjdbclearn.domain.bo; import java.util.collection;/*** El objeto de colección devuelve el resultado** @author yang gaochao* @since 2018-03-09*/RESTCOLLACIÓN PÚBLICA RESTCOLLECTIONSULT <T> {Resultado de una cadena privada; mensaje de cadena privada; colección privada <t> elementos; public String getResult () {return resultado; } public void setResult (resultado de cadena) {this.result = resultado; } public String getMessage () {Mensaje de retorno; } public void setMessage (mensaje de cadena) {this.message = mensaje; } colección pública <t> getItems () {return items; } public void setItems (colección <t> elementos) {this.items = elementos; }} Desarrollo de la capa de persistencia de datos
Definición de la interfaz de la capa de persistencia de datos del usuario
paquete com.yanggaochao.springboot.learn.springbootjdbclearn.dao; import com.yanggaochao.springboot.learn.springbootjdbclearn.domain.dao.userdo; import java.util.list;/*** interfacio de la capa* @aOthor* @ACHORA* @ATHORA* 2018-03-09*/Public Interface UserDao {/*** Guarde un nuevo usuario en la base de datos** @param OBJETO DE USUARIO para guardar* @return si la ganancia muscular es exitosa*/boolean add (userdo user); / *** Actualizar a un usuario en la base de datos*** @param Usuario usuario del usuario para actualizar* @return si la actualización es exitosa*/ actualización booleana (usuario userdo); / *** Eliminar un usuario especificado** @param ID La identidad del usuario para eliminar* @return si el eliminación es exitoso*/ boolean delete (id ID); / *** Consulta exacta de un usuario especificado** @param ID La identidad del usuario para consultar* @return si se puede consultar, devolver la información del usuario, de lo contrario devuelve nulo*/ Userdo ubicación (ID larga); / *** Consulta al usuario por nombre** @param Nombre El nombre es confuso* @return Lista de usuarios para consultar*/ list <sererdo> MatchName (nombre de cadena);} Implementación de la capa de persistencia de datos del usuario
paquete com.yanggaochao.springboot.learn.springbootjdbclearn.dao.impl; import com.yanggaochao.springboot.learn.springbootjdbclearn.dao.userdao; com.yanggaochao.springboot.learn.springbootjdbclearn.domain.dao.userdo; import org.springframework.beans.factory.annotation.aUtowired; import org.springframework.jdbc.core.jdbctemplate; importar; org.springframework.jdbc.support.rowset.sqlrowset; import org.springframework.stereotype.repository; import java.util.arrayList; import java.util.list;/*** clases de implementación de acceso a objetos de usuario** @author yang gaochao* @sce de 2018 */@RepositoryPublic Class UserDaojdbctEmpletImpl implementa UserDao {private final jdbctemplate jdbctemplate; @AUTOWIRED USUARIO PUBLICODAJDBCTEMPLETIMPL (JDBCTEMPLATE JDBCTEMPLATE) {this.jdbctemplate = jdbctemplate; } @Override public boolean add (userdo user) {string sql = "inserte en valores de auth_user (uuid, name) (?,?)"; return jdbctemplate.update (sql, user.getid (), user.getName ())> 0; } @Override public boolean Update (userdo user) {String sql = "Update auth_user set name =? Donde uuid =?"; return jdbctemplate.update (sql, user.getName (), user.getID ())> 0; } @Override public boolean delete (ID largo) {String sql = "Eliminar de auth_user donde uuid =?"; return jdbctemplate.update (sql, id)> 0; } @Override public userDo localizar (ID de Long) {String sql = "Seleccionar * de Auth_user donde uuid =?"; Sqlrowset rs = jdbctemplate.Queryforrowset (sql, id); if (rs.next ()) {return GenerateEntity (rs); } return null; } @Override Public List <SererDo> MatchName (Nombre de cadena) {String sql = "Seleccionar * de Auth_user donde Name Like?"; Sqlrowset rs = jdbctemplate.queryFoROWSET (SQL, "%" + nombre + "%"); List <sererDo> users = new ArrayList <> (); while (rs.next ()) {users.Add (generateEntity (rs)); } Devuelve usuarios; } private userdo GenerateEntity (sqlrowset rs) {userdo weChatPay = new UserDo (); wechatpay.setid (rs.getLong ("uuid")); wechatpay.setName (rs.getString ("nombre")); devolver wechatpay; }} Aquí primero usamos Annotation @Repository para indicar que esta es una clase de la capa de persistencia de datos, y SpringBoot instanciará automáticamente esta clase. Luego agregue un @Autowired al constructor. Cuando SpringBoot instancia esta clase, inyectará automáticamente la instancia de JDBCTemplet en esta clase. Aquí, la instancia JDBCTemplet se configura automáticamente mediante SpringBoot en función de la configuración relacionada con la fuente de datos en aplicaciones.properties. De acuerdo con el algoritmo de SpringBoot para configurar automáticamente las fuentes de datos, la fuente de datos que se configurará aquí es Hikaricp.
El resto es como el desarrollo ordinario de Spring JDBCTemplet. Al convertir manualmente entre objetos y bases de datos SQL por programadores, los usuarios pueden ser agregados, modificados, eliminados, combinados confusos, consulta precisa y otras funciones.
Desarrollo de la capa comercial de datos
Definición de la interfaz de la capa de servicio de datos
paquete com.yanggaochao.springboot.learn.springbootjdbclearn.service; import com.yanggaochao.springboot.learn.springbootjdbclearn.domain.dao.userdo; import java.util.list;/*** User Service Interface* @aOCHORE* @ @ @ @ @@AChor Yang* @ @ @@AxinAngAnginNang GaConEd GaCon. 2018-03-09 */Public Interface UserService {userdo add (userdo user); UserDo Update (userdo user); Eliminar booleano (identificación larga); UserDo ubicar (ID largo); Lista <sererDo> MatchName (nombre de cadena);} Implementación de la capa de servicio de datos
paquete com.yanggaochao.springboot.learn.springbootjdbclearn.service.impl; import com.yanggaochao.springboot.learn.springbootjdbclearn.dao.userdao; import com.yanggaochao.springboot.learn.springbootjdbclearn.domain.dao.userdo; import org.springframework.stereotype.service; import java.util.date; import java.util.list;/*** Clase de implementación de la capa empresarial de usuarios** @author yang gaochao* @since 2018-03-09*/ @ServicePublic ClasserSerSerViceImpl implementa el servicio de usuarios de usuarios {usuarios finales privados @serdao; @AUTOWIRED Public UserServiceImpl (userdao userDao) {this.userdao = userDao; } @Override public userdo add (userdo user) {user.setID (new Date (). GetTime ()); if (userdao.add (user)) {return user; } return null; } @Override public UserDo Update (UserDo User) {if (userDao.Update (User)) {Returnation (user.getID ()); } return null; } @Override public boolean delete (ID largo) {return userDao.Delete (id); } @Override Public UserDo Ubicación (ID Long) {return userDao.locate (id); } @Override Public List <SererDo> MatchName (Nombre de cadena) {return userDao.matchName (nombre); }} Aquí, esta clase de implementación se declara como una clase de nivel comercial a través de una anotación @Service. UserDao de la capa de persistencia permite a SpringBoot instanciar esta clase de capa comercial a través de @aUtowired, e inyectar automáticamente la clase de capa de persistencia correspondiente en esta clase ejecutiva.
Aquí, al agregar objetos de usuario, al establecer la identificación del usuario, un número de tiempo de milisegundos simplemente se usa como identificación. Durante el proceso de desarrollo real, este lugar debe utilizar un mecanismo globalmente único para garantizar que este logotipo no se pueda repetir.
Desarrollo de la capa de servicio externo
paquete com.yanggaochao.springboot.learn.springbootjdbclearn.web; import com.yanggaochao.springboot.learn.springbootjdbclearn.domain.bo.restcollectionresult; import; com.yanggaochao.springboot.learn.springbootjdbclearn.domain.dao.userdo; import org.springframework.web.bind.annotation.*; import java.util.list;/*** Interfaz REST http de usuario** @author yang gaochao* @since 2018-03-09*/ @restscontroller @requestmapping ("api/v1/user") public classapi {@autowired privado usuarios de usuarios de usuarios; @RequestMapping (value = "/add", método = requestmethod.post) public RestitemResult <UsaterDo> add (@RequestBody UserDo User) {RestitemResult <userdo> result = new RestItemResult <> (); user = Userservice.Add (usuario); if (user! = null) {result.setItem (usuario); resultado.setResult ("éxito"); } else {result.setMessage ("New User falló"); resultado.setResult ("falla"); } resultado de retorno; } @RequestMapping (value = "/update", método = requestmethod.post) public RestitemResult <sererDo> update (@RequestBody UserDo User) {RestitemResult <UsaterDo> result = New RestITEMRESULT <> (); user = Userservice.Update (usuario); if (user! = null) {result.setItem (usuario); resultado.setResult ("éxito"); } else {result.setMessage ("UserDo no se pudo modificar el usuario"); resultado.setResult ("falla"); } resultado de retorno; } @RequestMapping (value = "/delete/{uuid}", método = requestMethod.get) public RestitemResult <SererDo> delete (@PathVariable Long Uuid) {RestitEMResultusult <UsatDo> Result = New RestITEMRESULT <> (); if (Userservice.Delete (uuid)) {result.setResult ("éxito"); } else {result.setMessage ("Eliminar el usuario fallido"); resultado.setResult ("falla"); } resultado de retorno; } @RequestMapping (value = "/localate/{uuid}", método = requestMethod.get) public RestitemResult <UnserDo> Locate (@PathVariable Long Uuid) {RestitEMResultusult <UsatDo> Result = New RestITEMRESULT <> (); Userdo user = Userservice.locate (UUID); if (user! = null) {result.setItem (usuario); resultado.setResult ("éxito"); } else {result.setMessage ("El usuario de consulta fallido"); resultado.setResult ("falla"); } resultado de retorno; } @RequestMapping (valor = "/match/{name}", método = requestMethod.get) public RestCollectionResult <UsatDo> Match (@PathVariable String Name) {RestCollectionResult <Usaterdo> result = New RestCollectionResult <> (); List <SererDo> Users = Userservice.matchName (nombre); result.setItems (usuarios); resultado.setResult ("éxito"); resultado de retorno; }} Aquí @RestController se utiliza para declarar que esta es una clase de interfaz HTTP REST. La ruta de llamada para cada interfaz se forma combinando @RequestMapping en la clase y @RequestMapping en el método. La propiedad del método en @requestMapping en el método declara el método llamado por http. @RequestBody Annotation convierte automáticamente el objeto JSON en los datos de publicación en un objeto POJO. @PathVariable convierte automáticamente datos en la ruta de URL HTTP en parámetros del método de servicio.
Prueba de interfaz HTTP REST
Pruebe El servicio HTTP REST se llama a través del HTTPClient de Apache Commons.
Http resst llama clases auxiliares
package com.yanggaochao.springboot.learn.springbootjdbclearn;import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;import org.apache.commons.httpclient.HttpClient;import org.apache.commons.httpclient.methods.GetMethod;import org.apache.commons.httpclient.methods.getmethod; import org.apache.commons.httpclient.methods.stringRequestentity; importar org.apache.commons.httpclient.params.httpmethodparams; importar java.io.bufferedreer; import java.io.iO.IO.InpTER.InpTER; java.io.inputstreamreader; import java.io.Reader; import java.util.map;/*** @author yang gaochao* @since 2018-03-09*/clase pública httpclienthelper {/*** Use el método get para iniciar una solicitud http** @param la url de http ttpt El texto de respuesta obtenido después de */ public String httpgetRequest (string url, map <string, string> encabezados) {try {httpclient httppclient = new httpclient (); GetMethod Method = new GetMethod (URL); Method.SetRequestHeader ("Content-type", "Application/JSON; Charset = UTF-8"); Method.getParams (). SetParameter (httpmethodparams.retry_handler, new DeFaulThttpMethodcryHandler (3, falso)); if (headers! = null) {for (clave de cadena: headers.keyset ()) {método.setRequestHeader (Key, Headers.get (Key)); }} int statuscode = httpclient.executemethod (método); if (statuscode == 200) {return parseInputStream (método.getResponseBodyAssTream ()); } else {System.out.println (url + "status =" + statuscode); }} catch (Exception e) {E.PrintStackTrace (); } return null; } / *** Use el método de publicación para iniciar una solicitud http** @param URL de la URL de http para acceder* @param datos de datos en la solicitud* @return el texto de respuesta obtenido después de acceder a http* / public string httpPosTrequest (url, string data, string <s string> String> Healers) {Pruebe {HttpClient HttpClient (); Método postmethod = nuevo postmetod (url); Method.SetRequestHeader ("Content-type", "Application/JSON; Charset = UTF-8"); Method.setRequestHeader ("AGENT USER", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebkit/537.36 (khtml, como gecko) Chrome/34.0.1847.131 Safari/537.36"); if (headers! = null) {for (clave de cadena: headers.keyset ()) {método.setRequestHeader (Key, Headers.get (Key)); }} método.setRequestEntity (new StringRequestEntity (datos, "JSON", "UTF-8")); int statuscode = httpclient.executemethod (método); if (statuscode == 200) {return parseInputStream (método.getResponseBodyAssTream ()); } else {System.out.println (url + "status =" + statuscode + parseInputStream (método.getResponseBodyAssTream ())); }} catch (Exception e) {E.PrintStackTrace (); } return null; } / *** analizando datos de texto de java.io.reader** @param rd java.io.eader objeto* @throws Exception lanza un error cuando ocurre un error* / private String parsereader (lector rd) lanza excepción {bufferedReader brd = nuevo bufferedreader (rd); Línea de cadena; StringBuilder ResponderSeContext = new StringBuilder (); while ((line = brd.readline ())! = null) {respuestaseContext.append (línea) .Append ("/n"); } //rd.close (); if (ResponseContext.Length ()> 0) {ResponseContext.DeletECheCarat (ResponseContext.Length () - 1); } return ResponderSeContext.ToString (); } / *** Poner datos de texto de la transmisión de entrada** @param es la secuencia de entrada* @throws Exception lanza una excepción cuando ocurre un error* / string private parseInputStream (inputStream is) arroja excepción {return parsereader (nuevo bufferedReader (nuevo inputStream Reader (IS))); }} Aquí implementamos principalmente el método para llamar al servicio HTTP REST utilizando métodos GET y POST.
Casos de prueba
Use Junit para ejecutar casos de prueba. Para implementar la prueba, agregamos la siguiente dependencia de Maven
<Spendency> <MoupRiD> Commons-HttpClient </groupid> <artifactid> commons-httpclient </artifactid> <versión> 3.1 </versión> <cope> test </scope> </pendency> <epardency> <proupid> org.codehaus.jettison </proupid> <artifactid> jettison </artifactid> <version> <cope> test </cope> </dependency>
paquete com.yanggaochao.springboot.learn.springbootjdbclearn; import org.codehaus.jettison.json.jsonObject; import org.junit.after; import org.junit.bebore; import org.junit.test; import java.net.urlencoder; import java.util.atilist; import; java.util.list;/** * Descripción: * * @author yang gaochao * @since 2018-03-09 */public class UserApitest {private String userAddurl = "http: // localhost: 3030/seguridad/api/v1/user/add"; String private userLocateUrl = "http: // localhost: 3030/seguridad/api/v1/user/localate/"; Cadena privada userDeleteUrl = "http: // localhost: 3030/seguridad/api/v1/user/delete/"; String private UserUpDateUrl = "http: // localhost: 3030/seguridad/api/v1/user/update"; Private String usermatchurl = "http: // localhost: 3030/seguridad/api/v1/user/coincidencia/"; JsonObject adduser = new JsonObject (); Long addUserID = nulo; List <Long> userIds = new ArrayList <> (); @Before public void antes () lanza la excepción {adduser.put ("nombre", "oveja hermosa"); JsonObject addResultjson = new JsonObject (new httpClientHelper (). HttpPostRequest (userAdDurl, addUser.ToString (), null)); afirmar ("éxito" .equals (adtresultjson.getString ("resultado")))); addUserID = addResultjson.getJsonObject ("item"). getLong ("id"); JsonObject user = new JsonObject (); user.put ("nombre", "cabra agradable"); addResultjson = new JsonObject (new httpClientHelper (). httpPostRequest (userAdDurl, user.ToString (), null)); afirmar ("éxito" .equals (adtresultjson.getString ("resultado")))); userIds.Add (addResultjson.getJsonObject ("item"). getLong ("id")); user.put ("Nombre", "Gray Wolf"); addResultjson = new JsonObject (new httpClientHelper (). httpPostRequest (userAdDurl, user.ToString (), null)); afirmar ("éxito" .equals (adtresultjson.getString ("resultado")))); userIds.Add (addResultjson.getJsonObject ("item"). getLong ("id")); } @Test public void topDateUser () lanza la excepción {jsonObject user = new jsonObject (); user.put ("nombre", "oveja smad"); user.put ("id", addUserID); nuevo httpClientHelper (). HttpPostEquest (UserUpdateUrl, user.ToString (), null); JsonObject locateresultjson = new JsonObject (new httpClientHelper (). HttpgetRequest (userLocateUrl + addUserID, null)); afirmar (user.getString ("nombre"). Equals (locaterSultjson.getJsonObject ("item"). getString ("name"))); } @Test public void testMatchUsCUser () lanza la excepción {jsonObject matchResultJson = new JSONObject (new httpClientHelper (). HttpgetRequest (usermatchurl + urlencoder.encode ("oveja", "utf-8"), null)); afirmar (matchResultjson.has ("elementos") && matchResultjson.getjsonArray ("elementos"). longitud () == 2); MatchResultjson = new JsonObject (new httpClientHelper (). httpgetRequest (usermatchurl + urlencoder.encode ("wolf", "utf-8"), null)); afirmar (matchResultjson.has ("elementos") && matchResultjson.getjsonArray ("elementos"). longitud () == 1); } @After public void After () lanza excepción {if (addUserID! = Null) {jsonObject DeleterSultjson = new JSonObject (new httpClientHelper (). HttpgetRequest (userDeleteUrl + addUserID, null)); afirmar ("éxito" .equals (deletreesultjson.getString ("resultado")))); } para (Long UserId: UserIds) {jsonObject DeleterSultjson = new JsonObject (new httpClientHelper (). httpgetRequest (userDeleteUrl + userId, null)); afirmar ("éxito" .equals (deletreesultjson.getString ("resultado")))); }}} Aquí, dos casos de prueba se declaran en @Test, uno prueba la función de modificación del usuario y la otra prueba de la función de consulta difusa del usuario. @Borfore declara los preparativos que se realizarán antes de ejecutar cada caso de prueba. Aquí primero insertamos tres datos en la base de datos, y al mismo tiempo, también probamos la función de agregar datos y una consulta precisa. @After declara la limpieza después de ejecutar cada caso de prueba. Aquí eliminamos principalmente los datos insertados anteriormente. La función de la eliminación del usuario se prueba sincrónicamente aquí.
posdata
Aquí hay un ejemplo completo de SpringBoot usando JDBC Templet. Si tiene experiencia en el uso de JDBC Templet en Spring, entonces el objetivo principal de reducir muchos trabajos de configuración en Spring.
El código involucrado en este artículo se ha subido a GitHub, y también puede descargarlo localmente
Resumir
Lo anterior es todo el contenido de este artículo. Espero que el contenido de este artículo tenga cierto valor de referencia para el estudio o el trabajo de todos. Si tiene alguna pregunta, puede dejar un mensaje para comunicarse. Gracias por su apoyo a Wulin.com.