行业知识
SpringBoot整合JPA
SpringBoot整合JPA
1、Spring Data JPA概述
SpringData:Spring 的一个子项目。用于简化数据库访问,支持NoSQL 和 关系数据存储。其主要目标是使数据
库的访问变得方便快捷。
JPA:JPA(Java Persistence API,Java持久化API),定义了对象关系映射(Object Relation Mapping,ORM)以及实
体对象持久化的标准接口。Hibernate实现了JPA的一个ORM框架。
JPA Spring Data:致力于减少数据访问层 (DAO) 的开发量,开发者唯一要做的,就只是声明持久层的接口,
其他都交给 Spring Data JPA 来完成。Spring Data JPA 是Spring基于ORM框架、JPA规范的基础上封装的一套JPA
应用框架。
2、SpringBoot整合JPA
2.1 建库建表
DROP TABLE IF EXISTS student ;CREATE TABLE student ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(100) DEFAULT NULL, sex varchar(100) DEFAULT NULL, age int(11) DEFAULT NULL, PRIMARY KEY ( id )) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
2.2 新建项目
目录结构如下:
2.3 添加相关依赖
4.0.0org.springframework.bootspring-boot-starter-parent2.5.4com.jpa.mysqlspring-data-jpa-mysql0.0.1-SNAPSHOTspring-data-jpa-mysqlspring-data-jpa-mysql1.8org.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starterorg.springframework.bootspring-boot-starter-testtestorg.springframework.bootspring-boot-starter-data-jpaorg.projectlomboklomboktruemysqlmysql-connector-javaorg.springframework.bootspring-boot-maven-plugin
2.4 修改application.properties配置文件
server.port=9000spring.datasource.driver-class-name=com.mysql.jdbc.Driverspring.datasource.url=jdbc:mysql://127.0.0.1:3306/testspring.datasource.username=rootspring.datasource.password=rootspring.jpa.show-sql=truespring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
2.5 编写entity
package com.jpa.mysql.entity;import lombok.Data;import javax.persistence.Column;import javax.persistence.Entity;import javax.persistence.Id;import javax.persistence.Table;import java.io.Serializable;@Data@Entity@Table(name = "student")public class Student implements Serializable { @Id @Column(name="id") private int id; @Column(name="name") private String name; @Column(name="sex") private String sex; @Column(name="age") private int age;} 2.6 编写dao
package com.jpa.gbase.dao;import com.jpa.gbase.entity.Student;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.stereotype.Repository;import java.util.List;@Repositorypublic interface StudentDao extends JpaRepository { List findByName(String name);} 2.6 编写service接口
package com.jpa.gbase.service;import com.jpa.gbase.entity.Student;import org.springframework.stereotype.Component;import java.util.List;@Componentpublic interface IStudentService { Student findById(Integer id); List findAll(); List findByName(String name); Student save(String name) throws Exception; void delete(Integer id) throws Exception;} 2.7 编写service实现类
package com.jpa.gbase.service.impl;import com.jpa.gbase.dao.StudentDao;import com.jpa.gbase.entity.Student;import com.jpa.gbase.service.IStudentService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import org.springframework.transaction.annotation.Transactional;import java.util.List;@Componentpublic class StudentServiceImpl implements IStudentService { @Autowired private StudentDao studentDao; @Override public Student findById(Integer id) { return studentDao.findById(id).get(); } @Override public List findAll() { return studentDao.findAll(); } @Override public List findByName(String name) { return studentDao.findByName(name); } @Override @Transactional public Student save(String name) throws Exception { Student student = new Student(); student.setName(name); student.setSex("M"); student.setAge(18); return studentDao.save(student); } @Override @Transactional public void delete(Integer id) throws Exception { studentDao.deleteById(id); }} 2.8 编写controller
package com.jpa.gbase.controller;import com.jpa.gbase.entity.Student;import com.jpa.gbase.service.IStudentService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RestController;import java.util.List;@RestController@RequestMapping(value = "/student")public class StudentController { @Autowired private IStudentService studentService; @GetMapping(value = "/findById/{id}") public Student findById(@PathVariable("id") Integer id) { return studentService.findById(id); } @GetMapping(value = "/findAll") public List findAll() { return studentService.findAll(); } @GetMapping(value = "/findByName/{name}") public List findByName(@PathVariable("name") String name) { return studentService.findByName(name); } @GetMapping(value = "/save/{name}") public Student save(@PathVariable("name") String name) { Student student = new Student(); try { student = studentService.save(name); } catch (Exception e) { e.printStackTrace(); } return student; } @GetMapping(value = "/delete/{id}") public boolean delete(@PathVariable("id") Integer id) { boolean flg = false; try { studentService.delete(id); flg = true; } catch (Exception e) { e.printStackTrace(); } return flg; }} 2.9 编写启动类
package com.jpa.mysql;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic class SpringDataJpaMysqlApplication {public static void main(String[] args) {SpringApplication.run(SpringDataJpaMysqlApplication.class, args);}} 3.测试
3.1 添加数据
http://localhost:9000/student/save/tom
3.2 根据id查询数据
http://localhost:9000/student/findById/1
3.3 根据名字查询数据
http://localhost:9000/student/findByName/tom
3.4 根据id删除数据
http://localhost:9000/student/delete/1
3.5 查询全部数据
http://localhost:9000/student/save/tom1http://localhost:9000/student/save/tom2http://localhost:9000/student/save/tom3http://localhost:9000/student/save/tom4http://localhost:9000/student/save/tom5http://localhost:9000/student/findAll
4、简单查询
基本查询也分为两种,一种是 Spring Data 默认已经实现,一种是根据查询的方法来自动解析成 SQL。
4.1 预先生成方法
Spring Boot Jpa 默认预先生成了一些基本的CURD的方法,例如:增、删、改等等
1、继承 JpaRepository
@Repositorypublic interface StudentDao extends JpaRepository {} 2、使用默认方法
@Autowiredprivate StudentDao studentDao;studentDao.findAll();studentDao.findOne(1l);studentDao.save(user);studentDao.delete(user);studentDao.count();studentDao.exists(1l);
4.2 自定义简单查询
自定义的简单查询就是根据方法名来自动生成 SQL。
主要的语法是 findByXX,readAByXX,queryByXX,countByXX, getByXX ,XX 代表属性名称。
Student findByName(String name);
也使用一些加一些关键字And 、 Or:
User findByNameOrSex(String username, int sex);
修改、删除、统计也是类似语法:
Long deleteById(Long id);Long countByName(String mame);
基本上 SQL 体系中的关键词都可以使用,例如:LIKE、 IgnoreCase、 OrderBy。
List findByNameLike(String name);Student findByNameIgnoreCase(String name);List findByNameOrderByAgeDesc(String name);
具体的关键字,使用方法和生产成SQL如下表所示:
| Keyword | Sample | JPQL snippet |
|---|---|---|
| And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
| Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
| Is,Equals | findByFirstnameIs,findByFirstnameEquals | … where x.firstname = ?1 |
| Between | findByStartDateBetween | … where x.startDate between ?1 and ?2 |
| LessThan | findByAgeLessThan | … where x.age |
| LessThanEqual | findByAgeLessThanEqual | … where x.age ?1 |
| GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 |
| After | findByStartDateAfter | … where x.startDate > ?1 |
| Before | findByStartDateBefore | … where x.startDate |
| IsNull | findByAgeIsNull | … where x.age is null |
| IsNotNull,NotNull | findByAge(Is)NotNull | … where x.age not null |
| Like | findByFirstnameLike | … where x.firstname like ?1 |
| NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 |
| StartingWith | findByFirstnameStartingWith | … where x.firstname like ?1 (parameter bound with appended %) |
| EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1 (parameter bound with prepended %) |
| Containing | findByFirstnameContaining | … where x.firstname like ?1 (parameter bound wrapped in %) |
| OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
| Not | findByLastnameNot | … where x.lastname ?1 |
| In | findByAgeIn(Collection ages) | … where x.age in ?1 |
| NotIn | findByAgeNotIn(Collection age) | … where x.age not in ?1 |
| TRUE | findByActiveTrue() | … where x.active = true |
| FALSE | findByActiveFalse() | … where x.active = false |
| IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstame) = UPPER(?1) |
5、复杂查询
在实际的开发中我们需要用到分页、删选、连表等查询的时候就需要特殊的方法或者自定义 SQL。
5.1 分页查询
分页查询在实际使用中非常普遍了,Spring Boot Jpa 已经帮我们实现了分页的功能,在查询的方法中,需要传入
参数Pageable ,当查询中有多个参数的时候Pageable建议做为最后一个参数传入。
Page findALL(Pageable pageable);Page findByName(String name,Pageable pageable);
Pageable 是 Spring 封装的分页实现类,使用的时候需要传入页数、每页条数和排序规则。
@Testpublic void testPageQuery() throws Exception {int page=1,size=10;Sort sort = new Sort(Direction.DESC, "id"); Pageable pageable = new PageRequest(page, size, sort); studentDao.findALL(pageable); studentDao.findByName("tom", pageable);} 5.2 限制查询
有时候我们只需要查询前N个元素,或者只取前一个实体。
Student findFirstByOrderByNameAsc();Student findTopByOrderByAgeDesc();Page queryFirst10ByName(String name, Pageable pageable);List findFirst10ByName(String name, Sort sort);List findTop10ByName(String name, Pageable pageable);
5.3 自定义SQL查询
其实 Spring Data 觉大部分的 SQL 都可以根据方法名定义的方式来实现,但是由于某些原因我们想使用自定义的
SQL 来查询,Spring Data 也是完美支持的;在 SQL 的查询方法上面使用@Query注解,如涉及到删除和修改再需
要加上@Modifying,也可以根据需要添加 @Transactional对事物的支持,查询超时的设置等。
@Modifying@Query("update student stu set stu.name = ?1 where stu.id = ?2")int modifyNameById(String name, Long id);@Transactional@Modifying@Query("delete from student where id = ?1")void deleteById(Long id);@Transactional(timeout = 10)@Query("select stu from student stu where stu.id = ?1")User findById(Long id); 5.4 多表查询
多表查询 Spring Boot Jpa 中有两种实现方式,第一种是利用 Hibernate 的级联查询来实现,第二种是创建一个结
果集的接口来接收连表查询后的结果,这里主要第二种方式。
首先需要定义一个结果集的接口类:
public interface HotelSummary {City getCity();String getName();Double getAverageRating();default Integer getAverageRatingRounded() {return getAverageRating() == null ? null : (int) Math.round(getAverageRating());}} 查询的方法返回类型设置为新创建的接口:
@Query("select h.city as city, h.name as name, avg(r.rating) as averageRating "- "from Hotel h left outer join h.reviews r where h.city = ?1 group by h")Page findByCity(City city, Pageable pageable);@Query("select h.name as name, avg(r.rating) as averageRating "- "from Hotel h left outer join h.reviews r group by h")Page findByCity(Pageable pageable); 使用:
Page hotels = this.hotelRepository.findByCity(new PageRequest(0, 10, Direction.ASC, "name"));for(HotelSummary summay:hotels){System.out.println("Name" +summay.getName());} 在运行中 Spring 会给接口(HotelSummary)自动生产一个代理类来接收返回的结果,代码汇总使用 getXX
的形式来获取。
6、使用枚举
使用枚举的时候,我们希望数据库中存储的是枚举对应的 String 类型,而不是枚举的索引值,需要在属性上面添
加@Enumerated(EnumType.STRING) 注解
@Enumerated(EnumType.STRING) @Column(nullable = true)private UserType type;
7、不需要和数据库映射的属性
正常情况下我们在实体类上加入注解@Entity,就会让实体类和表相关连如果其中某个属性我们不需要和数据库
来关联只是在展示的时候做计算,只需要加上@Transient属性既可。
@Transientprivate String userName;
8、多数据源的支持
8.1 同源数据库的多源支持
日常项目中因为使用的分布式开发模式,不同的服务有不同的数据源,常常需要在一个项目中使用多个数据源,因
此需要配置 Spring Boot Jpa 对多数据源的使用,一般分一下为三步:
-
1 配置多数据源
-
2 不同源的实体类放入不同包路径
-
3 声明不同的包路径下使用不同的数据源、事务支持
8.2 异构数据库多源支持
比如我们的项目中,即需要对 mysql 的支持,也需要对 Mongodb 的查询等。
实体类声明@Entity 关系型数据库支持类型,声明@Document 为 Mongodb 支持类型,不同的数据源使用不同的
实体就可以了。
interface PersonRepository extends Repository { …}@Entitypublic class Person { …}interface UserRepository extends Repository { …}@Documentpublic class User { …}但是,如果 User 用户既使用 Mysql 也使用 Mongodb 呢,也可以做混合使用。
interface JpaPersonRepository extends Repository { …}interface MongoDBPersonRepository extends Repository { …}@Entity@Documentpublic class Person { …}也可以通过对不同的包路径进行声明,比如 A 包路径下使用 mysql,B 包路径下使用 MongoDB。
@EnableJpaRepositories(basePackages = "com.neo.repositories.jpa")@EnableMongoRepositories(basePackages = "com.neo.repositories.mongo")interface Configuration { }9、多数据源使用案例
9.1 导入pom依赖
4.0.0 org.springframework.boot spring-boot-starter-parent 2.5.4 com.example spring-boot-multi-jpa 0.0.1-SNAPSHOT spring-boot-multi-jpa spring-boot-multi-Jpa UTF-8 1.8 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter-data-jpa mysql mysql-connector-java org.projectlombok lombok true junit junit test org.springframework.boot spring-boot-maven-plugin
9.2 配置文件
spring.datasource.primary.jdbc-url=jdbc:mysql://localhost:3306/test1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=truespring.datasource.primary.username=rootspring.datasource.primary.password=rootspring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driverspring.datasource.secondary.jdbc-url=jdbc:mysql://localhost:3306/test2?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=truespring.datasource.secondary.username=rootspring.datasource.secondary.password=rootspring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driverspring.jpa.show-sql=truespring.jpa.properties.hibernate.hbm2ddl.auto=createspring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialectspring.jpa.properties.hibernate.format_sql=true
9.3 实体类
package com.example.springbootmultijpa.model;import lombok.AllArgsConstructor;import lombok.Getter;import lombok.NoArgsConstructor;import lombok.Setter;import javax.persistence.*;import java.io.Serializable;@Getter@Setter@Entity@NoArgsConstructor@AllArgsConstructorpublic class User implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private long id; @Column(nullable = false, unique = true) private String userName; @Column(nullable = false) private String passWord; @Column(nullable = false, unique = true) private String email; @Column(nullable = true, unique = true) private String nickName; @Column(nullable = false) private String regTime; public User(String userName, String passWord, String email, String nickName, String regTime) { this.userName = userName; this.passWord = passWord; this.email = email; this.nickName = nickName; this.regTime = regTime; }}9.4 config
package com.example.springbootmultijpa.config;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties;import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings;import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.boot.jdbc.DataSourceBuilder;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import javax.sql.DataSource;import java.util.Map;@Configurationpublic class DataSourceConfig { @Autowired private JpaProperties jpaProperties; @Autowired private HibernateProperties hibernateProperties; @Bean(name = "primaryDataSource") @Primary @ConfigurationProperties("spring.datasource.primary") public DataSource firstDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "secondaryDataSource") @ConfigurationProperties("spring.datasource.secondary") public DataSource secondDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "vendorProperties") public Map getVendorProperties() { return hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(), new HibernateSettings()); }}package com.example.springbootmultijpa.config;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import org.springframework.data.jpa.repository.config.EnableJpaRepositories;import org.springframework.orm.jpa.JpaTransactionManager;import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;import org.springframework.transaction.PlatformTransactionManager;import org.springframework.transaction.annotation.EnableTransactionManagement;import javax.persistence.EntityManager;import javax.sql.DataSource;import java.util.Map;@Configuration@EnableTransactionManagement@EnableJpaRepositories( entityManagerFactoryRef = "entityManagerFactoryPrimary", transactionManagerRef = "transactionManagerPrimary", basePackages = {"com.example.springbootmultijpa.repository.test1"})//设置dao(repo)所在位置public class PrimaryConfig { @Autowired @Qualifier("primaryDataSource") private DataSource primaryDataSource; @Autowired @Qualifier("vendorProperties") private Map vendorProperties; @Bean(name = "entityManagerFactoryPrimary") @Primary public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary(EntityManagerFactoryBuilder builder) { return builder .dataSource(primaryDataSource) .properties(vendorProperties) .packages("com.example.springbootmultijpa.model") //设置实体类所在位置 .persistenceUnit("primaryPersistenceUnit") .build(); } @Bean(name = "entityManagerPrimary") @Primary public EntityManager entityManager(EntityManagerFactoryBuilder builder) { return entityManagerFactoryPrimary(builder).getObject().createEntityManager(); } @Bean(name = "transactionManagerPrimary") @Primary PlatformTransactionManager transactionManagerPrimary(EntityManagerFactoryBuilder builder) { return new JpaTransactionManager(entityManagerFactoryPrimary(builder).getObject()); }}package com.example.springbootmultijpa.config;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.jpa.repository.config.EnableJpaRepositories;import org.springframework.orm.jpa.JpaTransactionManager;import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;import org.springframework.transaction.PlatformTransactionManager;import org.springframework.transaction.annotation.EnableTransactionManagement;import javax.persistence.EntityManager;import javax.sql.DataSource;import java.util.Map;@Configuration@EnableTransactionManagement@EnableJpaRepositories( entityManagerFactoryRef = "entityManagerFactorySecondary", transactionManagerRef = "transactionManagerSecondary", basePackages = {"com.example.springbootmultijpa.repository.test2"})public class SecondaryConfig { @Autowired @Qualifier("secondaryDataSource") private DataSource secondaryDataSource; @Autowired @Qualifier("vendorProperties") private Map vendorProperties; @Bean(name = "entityManagerFactorySecondary") public LocalContainerEntityManagerFactoryBean entityManagerFactorySecondary(EntityManagerFactoryBuilder builder) { return builder .dataSource(secondaryDataSource) .properties(vendorProperties) .packages("com.example.springbootmultijpa.model") .persistenceUnit("secondaryPersistenceUnit") .build(); } @Bean(name = "entityManagerSecondary") public EntityManager entityManager(EntityManagerFactoryBuilder builder) { return entityManagerFactorySecondary(builder).getObject().createEntityManager(); } @Bean(name = "transactionManagerSecondary") PlatformTransactionManager transactionManagerSecondary(EntityManagerFactoryBuilder builder) { return new JpaTransactionManager(entityManagerFactorySecondary(builder).getObject()); }}9.5 Repository
package com.example.springbootmultijpa.repository.test1;import com.example.springbootmultijpa.model.User;import org.springframework.data.jpa.repository.JpaRepository;public interface UserTest1Repository extends JpaRepository { User findById(long id); User findByUserName(String userName); User findByUserNameOrEmail(String username, String email);}package com.example.springbootmultijpa.repository.test2;import com.example.springbootmultijpa.model.User;import org.springframework.data.jpa.repository.JpaRepository;public interface UserTest2Repository extends JpaRepository { User findById(long id); User findByUserName(String userName); User findByUserNameOrEmail(String username, String email);}9.6 启动类
package com.example.springbootmultijpa;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic class SpringBootMultiJpaApplication { public static void main(String[] args) { SpringApplication.run(SpringBootMultiJpaApplication.class, args); }}9.7 测试
package com.example.springbootmultijpa.repository;import com.example.springbootmultijpa.model.User;import com.example.springbootmultijpa.repository.test1.UserTest1Repository;import com.example.springbootmultijpa.repository.test2.UserTest2Repository;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;import javax.annotation.Resource;import java.text.DateFormat;import java.util.Date;@RunWith(SpringRunner.class)@SpringBootTestpublic class UserRepositoryTests { @Resource private UserTest1Repository userTest1Repository; @Resource private UserTest2Repository userTest2Repository; @Test public void testSave() throws Exception { Date date = new Date(); DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG); String formattedDate = dateFormat.format(date); userTest1Repository.save(new User("aa", "aa123456", "aa@126.com", "aa", formattedDate)); userTest1Repository.save(new User("bb", "bb123456", "bb@126.com", "bb", formattedDate)); userTest2Repository.save(new User("cc", "cc123456", "cc@126.com", "cc", formattedDate)); } @Test public void testDelete() throws Exception { userTest1Repository.deleteAll(); userTest2Repository.deleteAll(); } @Test public void testBaseQuery() { Date date = new Date(); DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG); String formattedDate = dateFormat.format(date); User user = new User("ff", "ff123456", "ff@126.com", "ff", formattedDate); userTest1Repository.findAll(); userTest2Repository.findById(3l); userTest2Repository.save(user); user.setId(2l); userTest1Repository.delete(user); userTest1Repository.count(); userTest2Repository.findById(3l); }}运行testSave()得到的结果:
运行testBaseQuery()得到的结果:
运行testDelete()得到的结果: