文章40 | 阅读 20042 | 点赞0
Spring是一个分层的JavaSE/EEfull-stack(一站式)轻量级开源框架。它针对JavaEE三层中的每一层都提供了不同的解决技术,在dao层,Spring提供了JDBC模板的技术,可对数据库进行CRUD操作。Spring提供了很多持久层技术的模板类简化了编程,如下图所示。
我这里说明一下:Spring框架对不同的持久层技术做了封装,如对传统的JDBC使用JdbcTemplate进行了封装,对Hibernate框架使用HibernateTemplate进行了封装。JdbcTemplate对JDBC进行了简单封装,使用类似于dbutils,但是使用并没有dbutils方便,只是提供了一种实现的方式而已。下面咱就来入门一下Spring的JDBC模板开发。
首先创建一个动态web项目,例如spring_demo03_jdbc,然后导入Spring框架相关依赖jar包,要导入哪些jar包呢?
以上两个jar包中,我们要知道spring-jdbc-4.2.4.RELEASE.jar包才是最主要的。
创建数据库和表的sql语句如下所示。
create database spring4_demo03;
use spring4_demo03;
create table account(
id int primary key auto_increment,
name varchar(20),
money double
);
在src目录下创建一个com.meimeixia.jdbc.demo01包,并在该包下编写一个JdbcDemo01单元测试类。然后,编写一个demo01方法来向数据库中保存数据。
package com.meimeixia.jdbc.demo01;
import org.junit.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
/** * JDBC模板的使用 * @author liayun * */
public class JdbcDemo01 {
//JDBC模板的使用类似于DbUtils
@Test
public void demo01() {
//创建一个连接池(Spring内置的连接池)
DriverManagerDataSource dataSource = new DriverManagerDataSource();
//你要连接哪个数据库啊?你的用户名和密码是啥?
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///spring4_demo03");
dataSource.setUsername("root");
dataSource.setPassword("liayun");
//创建JDBC模板
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
// jdbcTemplate.setDataSource(dataSource);
jdbcTemplate.update("insert into account values(null, ?, ?)", "李元芳", 10000d);
}
}
温馨提示:JDBC模板依赖连接池获得数据库连接,所以必须先构造连接池,然后再创建JdbcTemplate模板类对象,而且还须用到JdbcTemplate模板类的update方法。
以上红框框中的方法中有两个参数,第一个参数是sql语句,第二个参数是传递的参数值,Object类型的可变参数。
在JdbcDemo01单元测试类中,我们手动创建了连接池和模板类,这样感觉有点不好,还是将手动创建的这些类都交给Spring去管理比较好。这样的话,首先,咱得引入Spring的配置文件,一开始applicationContext.xml文件的内容肯定是空的,只不过包含了各种schema约束,下面我给出的applicationContext.xml文件包含的schema约束应该是最全面的。
<?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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
</beans>
然后,我们就可以在Spring配置文件中配置连接池和模板类了,即在Spring配置文件中添加如下配置。
<?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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 配置Spring的内置连接池~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!-- 属性注入 -->
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql:///spring4_demo03" />
<property name="username" value="root" />
<property name="password" value="liayun" />
</bean>
<!-- 配置Spring的JDBC模板,它还得需要使用一个连接池 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 属性注入 -->
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
接着,咱还要记得在src目录下引入Log4j的配置文件(log4j.properties),也就是日志记录文件,该文件内容如下:
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.err
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=c\:mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### set log levels - for more verbose logging change 'info' to 'debug' ###
# error warn info debug trace
log4j.rootLogger= info, stdout
紧接着,我们通过Spring来整合JUnit进行单元测试,即在com.meimeixia.jdbc.demo01包下创建一个JdbcDemo02的单元测试类。
package com.meimeixia.jdbc.demo01;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JdbcDemo02 {
//注入JDBC模板(JdbcTemplate),
@Resource(name="jdbcTemplate")
private JdbcTemplate jdbcTemplate;
//基本的保存操作
@Test
public void demo01() {
jdbcTemplate.update("insert into account values(null, ?, ?)", "李青霞", 10000d);
}
}
温馨提示:由于是通过Spring来整合JUnit进行单元测试的,所以在项目中还应导入spring-tx-4.2.4.RELEASE.jar包。
最后,运行以上demo01单元测试方法,发现Eclipse控制台会报如下异常。
出现该异常的原因是咱们的项目中还没有引入aop的jar包,所以要想解决该问题,咱还得引入spring-aop-4.2.4.RELEASE.jar包。这时,再次运行以上demo01单元测试方法,你就会发现测试通过了,并向数据库表中添加了一条记录。
在实际开发中,一般都会用Spring配置开源的数据库连接池,所以下面我就来重点介绍在Spring中如何配置那些开源的数据库连接池。
首先,导入DBCP连接池所须的jar包。
然后,在Spring配置文件中配置DBCP连接池和模板类,即在Spring配置文件中添加如下配置。
接着,对JdbcDemo02单元测试类中的demo01方法做出如下的修改,以便于测试DBCP连接池的使用。
package com.meimeixia.jdbc.demo01;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JdbcDemo02 {
//注入JDBC模板(JdbcTemplate),
@Resource(name="jdbcTemplate")
private JdbcTemplate jdbcTemplate;
//基本的保存操作
@Test
public void demo01() {
//jdbcTemplate.update("insert into account values(null, ?, ?)", "李青霞", 10000d);
//测试DBCP连接池的使用
jdbcTemplate.update("insert into account values(null, ?, ?)", "李如花", 10000d);
}
}
最后,运行以上demo01单元测试方法,你就会发现测试通过了,并向数据库表中添加了一条记录。
首先,导入C3P0连接池所须的jar包。
然后,在Spring配置文件中配置C3P0连接池和模板类,即在Spring配置文件中添加如下配置。
接着,对JdbcDemo02单元测试类中的demo01方法做出如下的修改,以便于测试C3P0连接池的使用。
package com.meimeixia.jdbc.demo01;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JdbcDemo02 {
//注入JDBC模板(JdbcTemplate),
@Resource(name="jdbcTemplate")
private JdbcTemplate jdbcTemplate;
//基本的保存操作
@Test
public void demo01() {
//jdbcTemplate.update("insert into account values(null, ?, ?)", "李青霞", 10000d);
//测试DBCP连接池的使用
//jdbcTemplate.update("insert into account values(null, ?, ?)", "李如花", 10000d);
//测试C3P0连接池的使用
jdbcTemplate.update("insert into account values(null, ?, ?)", "狄仁杰", 10000d);
}
}
最后,运行以上demo01单元测试方法,你就会发现测试通过了,并向数据库表中添加了一条记录。
在以上开源的数据库连接池的使用中,咱都是直接将数据库连接的配置信息(包括数据库驱动类的全名称、要连接的数据库、用户名以及密码等)通过属性注入的方式注入到了相应的连接池对象中。这么做,未免太儿戏了,在实际开发中,咱都是将这些数据库连接的配置信息抽取到一个属性文件中,例如jdbc.properties文件。于是,我们在src目录下新建这样一个属性文件,其内容如下图所示。
然后,咱就要在Spring的配置文件中引入以上属性文件了。咋引?这是个问题,在Spring的配置文件中引入外部的属性文件,一共有两种方式,它们分别是:
我们是肯定要用第二种方式的,因为一行代码就能搞定了!
接着,在Spring的配置文件中引入属性文件的值,下面我是以C3P0连接池的配置为例来进行演示的。
紧接着,对JdbcDemo02单元测试类中的demo01方法做出如下的修改,以便于测试C3P0连接池的使用。
package com.meimeixia.jdbc.demo01;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JdbcDemo02 {
//注入JDBC模板(JdbcTemplate),
@Resource(name="jdbcTemplate")
private JdbcTemplate jdbcTemplate;
//基本的保存操作
@Test
public void demo01() {
//jdbcTemplate.update("insert into account values(null, ?, ?)", "李青霞", 10000d);
//测试DBCP连接池的使用
//jdbcTemplate.update("insert into account values(null, ?, ?)", "李如花", 10000d);
//测试C3P0连接池的使用
//jdbcTemplate.update("insert into account values(null, ?, ?)", "狄仁杰", 10000d);
//测试C3P0连接池的使用,引入外部属性文件
jdbcTemplate.update("insert into account values(null, ?, ?)", "何菊花", 10000d);
}
}
最后,运行以上demo01单元测试方法,你就会发现测试通过了,并向数据库表中添加了一条记录。
在这一小节中,我会使用JdbcTemplate模板类来完成CRUD的操作,主要是对spring4_demo03数据库中的account表进行增删改查。
上面一直演示的就是添加操作,所以这里不再赘述。
在JdbcDemo02单元测试类中编写如下的一个demo02方法来测试修改操作。
package com.meimeixia.jdbc.demo01;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JdbcDemo02 {
//注入JDBC模板(JdbcTemplate),
@Resource(name="jdbcTemplate")
private JdbcTemplate jdbcTemplate;
//基本的保存操作
@Test
public void demo01() {
//测试C3P0连接池的使用,引入外部属性文件(二)
jdbcTemplate.update("insert into account values(null, ?, ?)", "何菊花", 10000d);
}
//修改操作
@Test
public void demo02() {
jdbcTemplate.update("update account set name = ?, money = ? where id = ?", "何金银", 20000d, 4);
}
}
在JdbcDemo02单元测试类中编写如下的一个demo03方法来测试删除操作。
package com.meimeixia.jdbc.demo01;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JdbcDemo02 {
//注入JDBC模板(JdbcTemplate),
@Resource(name="jdbcTemplate")
private JdbcTemplate jdbcTemplate;
//基本的保存操作
@Test
public void demo01() {
//测试C3P0连接池的使用,引入外部属性文件(二)
jdbcTemplate.update("insert into account values(null, ?, ?)", "何菊花", 10000d);
}
//修改操作
@Test
public void demo02() {
jdbcTemplate.update("update account set name = ?, money = ? where id = ?", "何金银", 20000d, 4);
}
//删除操作
@Test
public void demo03() {
jdbcTemplate.update("delete from account where id = ?", 4);
}
}
要想根据ID查询账户名字,我们需要在JdbcDemo02单元测试类中编写如下的一个demo04方法。
package com.meimeixia.jdbc.demo01;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JdbcDemo02 {
//注入JDBC模板(JdbcTemplate),
@Resource(name="jdbcTemplate")
private JdbcTemplate jdbcTemplate;
//基本的保存操作
@Test
public void demo01() {
//测试C3P0连接池的使用,引入外部属性文件(二)
jdbcTemplate.update("insert into account values(null, ?, ?)", "何菊花", 10000d);
}
//修改操作
@Test
public void demo02() {
jdbcTemplate.update("update account set name = ?, money = ? where id = ?", "何金银", 20000d, 4);
}
//删除操作
@Test
public void demo03() {
jdbcTemplate.update("delete from account where id = ?", 4);
}
//查询操作,只查询名字
@Test
public void demo04() {
String name = jdbcTemplate.queryForObject("select name from account where id = ?", String.class, 4);
System.out.println(name);
}
}
在JdbcDemo02单元测试类中编写如下的一个demo05方法来测试查询表中记录数的操作。
package com.meimeixia.jdbc.demo01;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JdbcDemo02 {
//注入JDBC模板(JdbcTemplate),
@Resource(name="jdbcTemplate")
private JdbcTemplate jdbcTemplate;
//基本的保存操作
@Test
public void demo01() {
//测试C3P0连接池的使用,引入外部属性文件(二)
jdbcTemplate.update("insert into account values(null, ?, ?)", "何菊花", 10000d);
}
//修改操作
@Test
public void demo02() {
jdbcTemplate.update("update account set name = ?, money = ? where id = ?", "何金银", 20000d, 4);
}
//删除操作
@Test
public void demo03() {
jdbcTemplate.update("delete from account where id = ?", 4);
}
//查询操作,只查询名字
@Test
public void demo04() {
String name = jdbcTemplate.queryForObject("select name from account where id = ?", String.class, 4);
System.out.println(name);
}
//统计个数
@Test
public void demo05() {
Long count = jdbcTemplate.queryForObject("select count(*) from account", Long.class);
System.out.println(count);
}
}
在查询表中记录数时,用到了JdbcTemplate模板类里面的queryForObject方法,如下图所示,用到的这个方法中有两个参数:第一个参数指sql语句,第二个参数指返回类型的class。
使用JdbcTemplate模板类进行查询操作的时候,还是比较麻烦的。前面我也说过JdbcTemplate对JDBC进行了简单封装,使用类似于dbutils,但是使用并没有dbutils方便,只是提供了一种实现的方式而已。我是为何这么说呢?因为在dbutils里面帮我们编写好了一些实现类,使用这些实现类可以封装结果,这些实现类都实现了ResultSetHandler接口。而使用JdbcTemplate模板类进行查询操作并返回数据结果的时候,虽然在JdbcTemplate模板类中有个接口,但是并没有提供实现类,故还需要自己编写实现类来封装结果。
首先,为了将查询出来的数据结果封装到一个实体对象中,咱得在src目录下新建一个com.meimeixia.jdbc.domain包,并在该包下编写一个Account类。
package com.meimeixia.jdbc.domain;
public class Account {
private Integer id;
private String name;
private Double money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
@Override
public String toString() {
return "Account [id=" + id + ", name=" + name + ", money=" + money + "]";
}
}
然后,就要来讲如何使用JdbcTemplate模板类进行查询操作并返回一个Account类的对象了。其中,你必然会用到JdbcTemplate模板类里面的queryForObject方法。
以上红框框中的方法中,有3个参数,下面我会对这3个参数小小地解释一下。
知道了queryForObject方法后,我们就要在JdbcDemo02单元测试类中编写如下的一个demo06方法来测试查询时返回一个Account类对象的操作。
package com.meimeixia.jdbc.demo01;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.meimeixia.jdbc.domain.Account;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JdbcDemo02 {
//注入JDBC模板(JdbcTemplate),
@Resource(name="jdbcTemplate")
private JdbcTemplate jdbcTemplate;
//基本的保存操作
@Test
public void demo01() {
//测试C3P0连接池的使用,引入外部属性文件(二)
jdbcTemplate.update("insert into account values(null, ?, ?)", "何菊花", 10000d);
}
//修改操作
@Test
public void demo02() {
jdbcTemplate.update("update account set name = ?, money = ? where id = ?", "何金银", 20000d, 4);
}
//删除操作
@Test
public void demo03() {
jdbcTemplate.update("delete from account where id = ?", 4);
}
//查询操作,只查询名字
@Test
public void demo04() {
String name = jdbcTemplate.queryForObject("select name from account where id = ?", String.class, 4);
System.out.println(name);
}
//统计个数
@Test
public void demo05() {
Long count = jdbcTemplate.queryForObject("select count(*) from account", Long.class);
System.out.println(count);
}
//查询一条记录,封装到一个对象中
@Test
public void demo06() {
Account account = jdbcTemplate.queryForObject("select * from account where id = ?", new MyRowMapper(), 4);
System.out.println(account);
}
class MyRowMapper implements RowMapper<Account> {
/* * rs:结果集 * rowNum:行号,现在你已经遍历到第几行了 */
@Override
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
Account account = new Account();
account.setId(rs.getInt("id"));
account.setName(rs.getString("name"));
account.setMoney(rs.getDouble("money"));
return account;
}
}
}
温馨提示:千万别忘了编写RowMapper接口的一个实现类哟!
在JdbcDemo02单元测试类中编写如下的一个demo07方法来测试查询时返回List集合的操作。
package com.meimeixia.jdbc.demo01;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.meimeixia.jdbc.domain.Account;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JdbcDemo02 {
//注入JDBC模板(JdbcTemplate),
@Resource(name="jdbcTemplate")
private JdbcTemplate jdbcTemplate;
//基本的保存操作
@Test
public void demo01() {
//测试C3P0连接池的使用,引入外部属性文件(二)
jdbcTemplate.update("insert into account values(null, ?, ?)", "何菊花", 10000d);
}
//修改操作
@Test
public void demo02() {
jdbcTemplate.update("update account set name = ?, money = ? where id = ?", "何金银", 20000d, 4);
}
//删除操作
@Test
public void demo03() {
jdbcTemplate.update("delete from account where id = ?", 4);
}
//查询操作,只查询名字
@Test
public void demo04() {
String name = jdbcTemplate.queryForObject("select name from account where id = ?", String.class, 4);
System.out.println(name);
}
//统计个数
@Test
public void demo05() {
Long count = jdbcTemplate.queryForObject("select count(*) from account", Long.class);
System.out.println(count);
}
//查询一条记录,封装到一个对象中
@Test
public void demo06() {
Account account = jdbcTemplate.queryForObject("select * from account where id = ?", new MyRowMapper(), 4);
System.out.println(account);
}
//查询多条记录,返回的是一个List<Account>集合
@Test
public void demo07() {
List<Account> list = jdbcTemplate.query("select * from account", new MyRowMapper());
for (Account account : list) {
System.out.println(account);
}
}
class MyRowMapper implements RowMapper<Account> {
/* * rs:结果集 * rowNum:行号,现在你已经遍历到第几行了 */
@Override
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
Account account = new Account();
account.setId(rs.getInt("id"));
account.setName(rs.getString("name"));
account.setMoney(rs.getDouble("money"));
return account;
}
}
}
在进行查询并返回List集合的操作时,须用到JdbcTemplate模板类里面的query方法,就是下面这两个。
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://liayun.blog.csdn.net/article/details/69790950
内容来源于网络,如有侵权,请联系作者删除!