美文网首页
Spring Boot学习笔记(四):Spring Boot 数

Spring Boot学习笔记(四):Spring Boot 数

作者: 简单一点点 | 来源:发表于2019-03-22 23:54 被阅读0次

Spring Data项目是Spring用来解决数据访问问题的一揽子解决方案。

全部章节传送门:
Spring Boot学习笔记(一):Spring Boot 入门基础
Spring Boot学习笔记(二):Spring Boot 运行原理
Spring Boot学习笔记(三):Spring Boot Web开发
Spring Boot学习笔记(四):Spring Boot 数据访问
Spring Boot学习笔记(五):Spring Boot 企业级开发
Spring Boot学习笔记(六):Spring Boot 应用监控

Spring Data JPA

Spring Data JPA是Spring Data的一个子项目,它提供了一套简化JPA开发的框架,用来简化数据库访问。同时提供了很多除了CRUD之外的功能,如分页、排序、复杂查询等等。

准备环境

创建数据表

在MySQL数据库中建立一个数据表t_person,用来进行后面的测试,并在里面随便添加几条数据。

create table t_person  (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name varchar(10),
    age INT,
    address VARCHAR(20)
) CHARACTER SET UTF8;

建立项目

建立一个springboot项目,在pom.xml中添加如下依赖。其中guava是一个工具包。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>18.0</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

配置基本属性

在application.properties里面配置数据源和JPA相关属性。

# 数据库相关
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/springstudy
spring.datasource.username=spring
spring.datasource.password=spring
spring.datasource.dirverClassName=com.mysql.jdbc.Driver
# 根据实体类自动维护数据库表结构的功能
spring.jpa.hibernate.ddl-auto=none
# 设置hibernate操作的时候在控制台显示真实的SQL语句
spring.jpa.show-sql=true
# 让控制器输出的json字符串格式更美观
spring.jackson.serialization.indent_output=true

其中,spring.jpa.hibernate.ddl-auto 提供根据实体类自动维护数据库表结构的功能,可选值包括:

  • create----每次运行该程序,没有表格会新建表格,表内有数据会清空。
  • create-drop----每次程序结束的时候会清空表。
  • update----每次运行程序,没有表格会新建表格,表内有数据不会清空,只会更新。
  • validate----运行程序会校验数据与数据库的字段类型是否相同,不同会报错。
  • none----不采取任何措施。

定义映射实体类

创建实体类Person,将数据表的字段映射过来。

其中,@Entity注解表明这个类是一个实体,任何Hibernate映射对象都要有这个注解,@Table注解用来映射表名,@Column注解用来映射属性名和字段名,不注解的时候可以自动映射,比如将name映射为NAME,将testName映射为TEST_NAME。

package com.wyk.datademo;

import javax.persistence.*;

@Entity
@Table(name = "t_person") //表名
public class Person {
    @Id //映射为数据库主键
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 生成方式为自增
    private Long id;
    private String name;
    private Integer age;
    private String address;

    public Person() {}

    public Person(Long id, String name, Integer age, String address) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.address = address;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

定义数据访问层

使用Spring Data JPA建立数据访问层需要定义一个继承JapRepository的接口。

package com.wyk.datademo;

import org.springframework.data.jpa.repository.JpaRepository;

public class PersonRespositary extends JpaRepository {
    ...
}

JpaRepository接口存在如下数据访问操作方法。

package org.springframework.data.jpa.repository;

import java.util.List;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.QueryByExampleExecutor;

@NoRepositoryBean
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
    List<T> findAll();

    List<T> findAll(Sort var1);

    List<T> findAllById(Iterable<ID> var1);

    <S extends T> List<S> saveAll(Iterable<S> var1);

    void flush();

    <S extends T> S saveAndFlush(S var1);

    void deleteInBatch(Iterable<T> var1);

    void deleteAllInBatch();

    T getOne(ID var1);

    <S extends T> List<S> findAll(Example<S> var1);

    <S extends T> List<S> findAll(Example<S> var1, Sort var2);
}

配置使用 Spring Data JPA

在Spring中,可以通过@EnableJpaRepositories注解来开启Spring Data JPA的支持,通过接收的value参数来扫描数据访问层所在包下的数据访问接口定义。

package com.wyk.datademo;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

import javax.persistence.EntityManagerFactory;

@Configuration
@EnableJpaRepositories("com.wyk.datademo")
public class JpaConfigurattion {
    public EntityManagerFactory entityManagerFactory() {
        ...
    }
    ...
}

在Sring Boot中,会进行自动配置,不需要添加如上配置代码。

定义查询方法

根据属性名查询

Spring Data JPA支持通过定义在Repository接口中的方法名来定义查询,而方法名是根据实体类的属性名来确定。

根据属性名来定义查询方法。

public interface PersonRepository extends JpaRepository<Person, Long> {
    /**
     * 通过名字查询
     * 相当于 select p from Person p where p.name=?1
     * @param name
     * @return
     */
    List<Person> findByName(String name);

    /**
     * 通过名字模糊查询
     * 相当于select p from Person p where p.name like ?1
     * @param name
     * @return
     */
    List<Person> findByNameLike(String name);

    /**
     * 通过名字和地址查询
     * 相当于 select p from Perosn p where p.name=?1 and p.address=?2
     * @param name
     * @param address
     * @return
     */
    List<Person> findByNameAndAddress(String name, String address);
}

还可以通过top和first等关键字来限制结果数量。

```java
/**
 * 查询符合条件的前10条数据
 * @param name
 * @return
 */
List<Person> findFirst10ByName(String name);

/**
 * 查询符合条件的奇拿30条数据
 * @param name
 * @return
 */
List<Person> findTop30ByName(String name);

使用JPA的NamedQuery查询

Spring Data JPA 支持用JPA的NamedQuery来定义查询方法,即一个名称映射一个查询语句。需要在实体类上添加@NamedQuery注解。

@Entity
@NamedQuery(name="Person.withNameAndAddressNamedQuery",
    query = "select p from Person p where p.name = ?1 and address=?2")
@Table(name = "t_person") //表名
public class Person {
    ...
}

查询使用如下语句。

/**
 * 使用NamdeQuery里定义的查询语句
 * @param name
 * @return
 */
Person withNameAndAddressQuery(String name, String address);

使用@Query查询

Spring Data JPA 还支持用@Query注解在接口的方法上实现查询,可以根据参数索引。

@Query("select p from Person p where p.name=?1 and p.address=?2")
Person withNameAndAddressQuery(String name, String address);

还可以使用参数的名称来匹配查询参数。

@Query("select p from Person p where p.address= :address")
List<Person> findByAddress(@Param("address")String address);

Spring Data JPA 支持@Modifying和@Query注解组合来事件更新查询。

@Modifying
@Transactional
@Query("update Person p set p.name=?1")
int setName(String name);

分页与排序

Spring Data JPA 也对排序和分页提供了支持。

/**
 * 查询结果排序
 * @param name
 * @param sort
 * @return
 */
List<Person> findByName(String name, Sort sort);

/**
 * 查询结果分页
 * @param name
 * @param pageable
 * @return
 */
Page<Person> findByName(String name, Pageable pageable);

使用排序:

List<Person> people = personRepository.findByName("haha", new Sort(Sort.Direction.ASC, "age"));

使用分页:

Page<Person> people2 = personRepository.findByName("haha", PageRequest.of(0, 10));

Page接口可有获取当前页面记录、总页数、总记录数等。

Specification

JPA 提供了基于准则查询的方式,即Criteria查询,可以用来进行复杂的动态查询。Spring Data JPA 提供了一个Specification接口,其中定义了一个toPredicate方法用来构造查询条件。

定义一个Criterial查询。其中Root用来获取需要查询的属性,通过CriteriaBuilder构造查询条件(例子中是来自北京的人).

public class CustomerSpecs {
    public static Specification<Person> personFromBeijing() {
        return new Specification<Person>() {
            @Override
            public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> criteriaQuery,
                                         CriteriaBuilder criteriaBuilder) {
                return criteriaBuilder.equal(root.get("address"), "北京");
            }
        };
    }
}

在接口类上需要实现JpaSpecificationExecutor接口。

public interface PersonRepository extends JpaRepository<Person, Long>,
        JpaSpecificationExecutor<Person> {
            ...
        }

使用的时候需要静态导入。

import static com.wyk.datademo.CustomerSpecs.*;

注入personRepository的Bean后可以调用方法。

List<Person> people = personRepository.findAll(perosnFromBeijing());

添加控制器

将PersonRepository注入到控制器。

package com.wyk.datademo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class DataController {

    //Spring Data JPA 已自动为你注册bean,所以可自动注入
    @Autowired
    PersonRepository personRepository;

    /**
     * 保存
     * @param name
     * @param address
     * @param age
     * @return
     */
    @RequestMapping("/save")
    public Person save(String name, String address, Integer age) {
        // save 支持批量保存
        Person p = personRepository.save(new Person(null, name, age, address));
        return p;
    }

    /**
     * 测试findByAddress
     * @param address
     * @return
     */
    @RequestMapping("/q1")
    public List<Person> q1(String name) {
        List<Person> people = personRepository.findByName(name);
        return people;
    }

    /**
     * 测试findByNameAndAddress
     * @param name
     * @param address
     * @return
     */
    @RequestMapping("/q2")
    public Person q2(String name, String address) {
        Person people = personRepository.findByNameAndAddress(name, address);
        return people;
    }

    /**
     * 测试withNameAndAddressQuery
     * @param name
     * @param address
     * @return
     */
    @RequestMapping("/q3")
    public Person q3(String name, String address) {
        Person people = personRepository.withNameAndAddressQuery(name, address);
        return people;
    }

    /**
     * 测试withNameAndAddressNamedQuery
     * @param name
     * @param address
     * @return
     */
    @RequestMapping("/q4")
    public Person q4(String name, String address) {
        Person people = personRepository.withNameAndAddressNamedQuery(name, address);
        return people;
    }

    /**
     * 测试排序
     * @return
     */
    @RequestMapping("/sort")
    public List<Person> sort() {
        List<Person> people = personRepository.findAll(new Sort(Sort.Direction.ASC, "age"));
        return people;
    }

    /**
     * 测试分页
     * @return
     */
    @RequestMapping("/page")
    public Page<Person> page() {
        Page<Person> pagePeople = personRepository.findAll(PageRequest.of(1, 2));
        return pagePeople;
    }
}

查看运行结果

运行项目,依次查看结果:

保存实体(http://localhost:8080/save?name=ss&address=Shanghai&age=25):

springboot-jpa-save.png

根据属性查询(http://localhost:8080/q1?name=xiaoming):

springboot-jpa-q1.png

根据多条属性查询(http://localhost:8080/q2?name=xiaoming&address=Beijing):

springboot-jpa-q2.png

根据@Query注解查询(http://localhost:8080/q3?name=xiaoming&address=Beijing):

springboot-jpa-q3.png

根据NamedQuery查询(http://localhost:8080/q4?name=xiaoming&address=Beijing):

springboot-jpa-q4.png

查询结果排序(http://localhost:8080/sort):

springboot-jpa-sort.png

查询结果分页(http://localhost:8080/sort):

springboot-jpa-page.png

自定义Repository的实现

我们可以通过Spring Data JPA的JpaRepository封装自己的数据库操作,提供给Repository接口使用。

定义Specification

首先需要定义Specification,本部分定制一个自动模糊查询:当值为字符型时使用like查询,其余类型使用等于查询,没有值就查询全部。

package com.wyk.datademo;

import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.SingularAttribute;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import static com.google.common.collect.Iterables.toArray;

public class CustomerSpecs {
    /**
     * 定义一个返回值为Specification的方法byAuto
     * @param entityManager
     * @param example
     * @param <T>
     * @return
     */
    public static <T> Specification<T> byAuto(final EntityManager entityManager,
                                              final T example) {
        // 获得当前实体类对象的类型
        final Class<T> type = (Class<T>)example.getClass();
        return new Specification<T>() {
            @Override
            public Predicate toPredicate(Root<T> root, CriteriaQuery<?> criteriaQuery,
                                         CriteriaBuilder criteriaBuilder) {
                // 新建Predicate列表存储构造的查询条件
                List<Predicate> predicates = new ArrayList<>();
                // 获取实体类的entityType,我们可以从中获得实体类的属性
                EntityType<T> entity = entityManager.getMetamodel().entity(type);
                //对实体类的属性进行循环
                for(Attribute<T, ?> attr : entity.getDeclaredAttributes()) {
                    // 获取实体类对象某一属性的值
                    Object attrValue =  getValue(example, attr);
                    if(attrValue != null) {
                        // 当前属性为字符类型的时候
                        if(attr.getJavaType() == String.class) {
                            // 当前字符不为空的情况下
                            if(!StringUtils.isEmpty(attrValue)) {
                                // 构造当前属性like查询条件,并添加条件列表
                                predicates.add(criteriaBuilder.like(root.get(attribute(entity, attr.getName(),
                                        String.class)), pattern((String) attrValue)));
                            } else {
                                // 构造属性和属性值equal查询条件,并添加到条件列表中
                                predicates.add(criteriaBuilder.equal(root.get(attribute(entity,
                                        attr.getName(), attrValue.getClass())), attrValue));
                            }

                        }
                    }
                }
                //将条件列表转换成Predicate
                return predicates.isEmpty() ? criteriaBuilder.conjunction() :
                        criteriaBuilder.and(toArray(predicates, Predicate.class));
            }

            /**
             * 通过反射获取实体类对象对应属性的属性值
             * @param example
             * @param attr
             * @param <T>
             * @return
             */
            private <T> Object getValue(T example, Attribute<T, ?> attr) {
                return ReflectionUtils.getField((Field) attr.getJavaMember(), example);
            }

            /**
             * 获取实体类当前属性的SingularAttribute
             * @param entity
             * @param fieldName
             * @param fieldClass
             * @param <E>
             * @param <T>
             * @return
             */
            private <E, T> SingularAttribute<T, E> attribute(EntityType<T> entity,
                                                            String fieldName, Class<E> fieldClass) {
                return entity.getDeclaredSingularAttribute(fieldName, fieldClass);
            }
        };
    }

    /**
     * 构造like的查询模式
     * @param str
     * @return
     */
    static private String pattern(String str) {
        return "%" + str + "%";
    }
}

定义Repository接口

定义一个继承JpaRepository的接口,使它具备JpaRepository接口的所有方法,还继承了JpaSpecificationExecutor,具备使用Specification的能力。

package com.wyk.datademo;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.NoRepositoryBean;

import java.io.Serializable;

@NoRepositoryBean //表示当前接口不是领域类的接口
public interface CustomRepository<T, ID extends Serializable> extends
        JpaRepository<T, ID>, JpaSpecificationExecutor<T> {

    Page<T> findByAuto(T example, Pageable pageable);
}

定义接口实现

定义一个实现前面接口的类,并继承SimpleJpaRepository,让我们可以使用SimpleJpaRepository中的方法。

package com.wyk.datademo;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;

import javax.persistence.EntityManager;
import java.io.Serializable;

import static com.wyk.datademo.CustomerSpecs.byAuto;

public class CustomRepositoryImpl <T, ID extends Serializable> extends
        SimpleJpaRepository<T, ID> implements CustomRepository<T, ID> {

    private final EntityManager entityManager;

    public CustomRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
        super(domainClass, entityManager);
        this.entityManager = entityManager;
    }

    /**
     * 实现用byAuto的条件查询,并提供分页查询
     * @param example
     * @param pageable
     * @return
     */
    @Override
    public Page<T> findByAuto(T example, Pageable pageable) {
        return findAll(byAuto(entityManager, example), pageable);
    }
}

定义repositoryFactoryBean

自定义repositoryFactoryBean扩展JpaRepositoryFactoryBean,可以从获得一个RepositoryFactory。

package com.wyk.datademo;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;

import javax.persistence.EntityManager;
import java.io.Serializable;

public class CustomRepositaryFactoryBean<T extends JpaRepository<S, ID>, S, ID
    extends Serializable> extends JpaRepositoryFactoryBean<T, S, ID> {

    public CustomRepositaryFactoryBean(Class<? extends T> repositoryInterface) {
        super(repositoryInterface);
    }

    @Override
    protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
        return new CustomRepositoryFactory(entityManager);
    }

    private static class CustomRepositoryFactory<S, ID extends Serializable>
        extends JpaRepositoryFactory {

        public CustomRepositoryFactory(EntityManager entityManager) {
            super(entityManager);
        }

        @Override
        @SuppressWarnings({"unchecked"})
        protected SimpleJpaRepository<?, ?> getTargetRepository(
                RepositoryInformation information, EntityManager entityManager) {// 获得当前自定义类的实现
            return new CustomRepositoryImpl<S, ID>((Class<S>) information.getDomainType(), entityManager);

        }
        /*
        @Override
        @SuppressWarnings({"unchecked"})
        protected <T, ID extends Serializable> SimpleJpaRepository<?, ?>
        getTargetRepository (RepositoryInformation information,
                             EntityManager entityManager) {
            return new CustomRepositoryImpl<T, ID>((Class<T>) information.getDomainType(),
                    entityManager);
        }
        */

        @Override
        protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {

            return CustomRepositoryImpl.class;
        }
    }
}

使用自定义仓库

让实体类的Repository继承自定义的Repository接口,即可使用自定义Repository中实现的功能。

public interface PersonRepository extends CustomRepository<Person, Long>,
        JpaSpecificationExecutor<Person> {
    ...
}

在控制器中添加测试方法。

/**
 * 测试自定义仓库
 * @param person
 * @return
 */
@RequestMapping("/auto")
public Page<Person> auto(Person person) {
Page<Person> pagePeople = personRepository.findByAuto(person, PageRequest.of(0, 10));
return pagePeople;
}

在运行类上使用@EnableJpaRepositories让自定义的Repoisitory生效。

@SpringBootApplication
@EnableJpaRepositories(repositoryFactoryBeanClass = CustomRepositaryFactoryBean.class)
public class DatademoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DatademoApplication.class, args);
    }

}

查看效果

运行程序,访问http://localhost:8080/auto, 无查询条件,查看结果。

springboot-jpa-auto1.png

访问http://localhost:8080/auto?address=h,进行模糊查询。

springboot-jpa-auto2.png

Spring Data REST

Spring Data REST是基于Spring Data的repository之上,可以将repository自动输出为REST资源。

配置Spring Data REST

Spring Data REST的配置是定义在RepositoryRestMvcConfiguration配置类中,我们可以通过继承此类或者直接在自己的配置类上@Import此配置类。

继承方式:

@Configuration
public class MyRepositoryRestMvcConfiguration extends RepositoryRestMvcConfiguration {
    @Override
    public RepositoryRestConfiguraiton config() {
        return super.config();
    }

    //其它可重写以config开头的方法
    ...
}

导入方式:

@Configuration
@Import(RepositoryRestMvcConfiguration.class) 
public class AppConfig {
    ...
}

Spring Boot对Spring Data REST的自动配置放置在rest包中.通过SpringBootRestConfiguration类的源码我们可以得出,Spring Boot已经为我们自动配置了RepositoryRestConfiguration,所以在Spring boot中使用Spring Data REST只需引入spring-boot-starter-data-rest的依赖,无须任何配置即可使用

Spring boot通过在application.properties中配置以spring.data.rest为前缀的属性来配置RepositoryRestConfiguration。

Spring Data REST实战

新建SpringBoot项目,与上例类似,依赖在前面的基础上增加REST(spring-boot-starter-data-rest)。application.properties的配置信息与前面一样。

添加同样的实体类Person.java,并定义实体类的Repository。其中,在Repository中的方法上添加注解@RestResource可以将方法暴漏为REST源。

package com.wyk.datarestdemo.repository;

import com.wyk.datarestdemo.bean.Person;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RestResource;

public interface PersonRepository extends JpaRepository<Person, Long> {
    @RestResource(path="nameStartsWith", rel="nameStartsWith")
    Person findByNameStartsWith(String name);
}

为了方便测试,我们使用一个工具Postman,可以在官网直接下载。

运行程序,打开Postman,在其中使用GET访问http://localhost:8080/persons/1, 收到如下返回。

springboot-rest-1.png

使用GET访问地址 http://localhost:8080/persons/search/nameStartsWith?name=Li, 用来测试方法。

springboot-rest-2.png

Spring Data Rest 还支持分页和排序,以及更新、保存、删除等多个操作,这里不再展示。

Spring Data Rest定制

定制根路径

前面提到相关配置都在application.properties中配置以spring.data.rest为前缀的属性来配置。默认访问路径是根路径,如果想修改,可以进行如下配置。

spring.data.rest.base-path=/api

定制节点路径

在上面的例子,我们使用 http://localhost:8080/persons 访问,这时Spring Data REST的默认规则,使用实体类加s形成路径。如果想对映射名称进行修改,需要在实体类Repository上使用@RepositoryRestResource 注解的path属性进行修改。

@RepsositoryRestResource(path="people")
public interface PersonRepository extends JpaRepository<Person, Long> {
    @RestResource(path="nameStartsWith", rel="nameStartsWith")
    Person findByNameStartsWith(String name);
}

这样访问地址就变成了 http://localhost:8080/api/people

声明式事务

Spring事务机制

Spring的事务机制是用统一的机制来处理不同数据访问技术的事务处理。Spring的事务机制提供了一个PlatformTransactionManager接口,不同的数据访问技术的事务使用不同的接口实现。

数据库访问技术 实现
JDBC DataSourceTransactionManager
JPA JpaTransactionManager
Hibernate HibernateTransactionManager
JDO JdoTransactionManager
分布式事务 JtaTransactionManager

注解事务行为

Spring支持声明式事务。即使用注解来选择需要使用事务的方法。它使用@Transactional注解在方法上表明该方法需要事务支持。如果@Transactional注解使用在类上,则此类的所有public方法都是开启事务的。

Spring提供了一个@EnableTransactionManagement注解在配置类上开启声明式事务支持。使用方式:

@Configuration
@EnableTransactionManagement
public class AppConfig {
    ...
}

@Transactional的属性如下表。

参数名称 功能描述 默认值
readOnly 该属性用于设置当前事务是否为只读事务 false
rollbackFor 该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。 Throwble的子类
noRollbackFor 该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。 Throwble的子类
propagation 该属性用于设置事务的传播行为。 REQUIRED
isolation 该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置。 DEFAULT
timeout 该属性用于设置事务的超时秒数 TIMEOUT_DEFAULT

SpringBoot事务支持

Spring Data JPA对所有默认的方法都开启了事务支持,且查询类事务默认启用readOnly=true属性。

Spring Boot会自动配置事务管理器,且会自动开启注解事务的支持。

使用和前面相同的例子,创建一个实体类Person和接口PersonRepository。

添加业务服务接口。

package com.wyk.datarestdemo.service;

import com.wyk.datarestdemo.bean.Person;

public interface DemoService {
    public Person savePersonWithRollBack(Person person);
    public Person savePersonWithoutRollBack(Person person);
}

添加业务服务实现。

package com.wyk.datarestdemo.service.impl;

import com.wyk.datarestdemo.bean.Person;
import com.wyk.datarestdemo.repository.PersonRepository;
import com.wyk.datarestdemo.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class DemoServiceImpl  implements DemoService {
    @Autowired
    PersonRepository personRepository;

    @Transactional(rollbackFor={IllegalArgumentException.class})
    public Person savePersonWithRollBack(Person person) {
        Person p = personRepository.save(person);

        if(person.getName().equals("wyk")) {
            throw new IllegalArgumentException("wyk已存在,数据将回滚");
        }
        return p;
    }

    @Transactional(noRollbackFor = {IllegalArgumentException.class})
    public Person savePersonWithoutRollBack(Person person) {
        Person p = personRepository.save(person);

        if(person.getName().equals("wyk")) {
            throw new IllegalArgumentException("wyk虽已存在,数据不会回滚");
        }
        return p;
    }
}

添加控制器。

package com.wyk.datarestdemo.controller;

import com.wyk.datarestdemo.bean.Person;
import com.wyk.datarestdemo.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyController {
    @Autowired
    DemoService demoService;

    @RequestMapping("/rollback")
    public Person rollback(Person person) {
        return demoService.savePersonWithRollBack(person);
    }

    @RequestMapping("/norollback")
    public Person noRollback(Person person) {
        return demoService.savePersonWithoutRollBack(person);
    }
}

运行程序,访问 http://localhost:8080/rollback?name=wyk&age=29 ,这时程序抛出异常。

java.lang.IllegalArgumentException: wyk已存在,数据将回滚
    at com.wyk.datarestdemo.service.impl.DemoServiceImpl.savePersonWithRollBack(DemoServiceImpl.java:20) ~[classes/:na]
    at com.wyk.datarestdemo.service.impl.DemoServiceImpl$$FastClassBySpringCGLIB$$2ef6f418.invoke(<generated>) ~[classes/:na]
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:749) ~[spring-aop-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    ...

查看数据库。

mysql> select * from t_person;
+----+----------+------+----------+
| id | name     | age  | address  |
+----+----------+------+----------+
|  1 | xiaoming |   22 | Beijing  |
|  2 | xiaohong |   21 | Beijing  |
|  3 | Peter    |   18 | New York |
|  4 | Jingjing |   18 | Hengshui |
|  5 | Lily     |   28 | Tianjin  |
|  6 | ss       |   25 | Shanghai |
+----+----------+------+----------+
6 rows in set (0.00 sec)

并没有插入成功。

改为访问 http://localhost:8080/norollback?name=wyk&age=29 ,这时程序同样抛出异常。

java.lang.IllegalArgumentException: wyk虽已存在,数据不会回滚
    at com.wyk.datarestdemo.service.impl.DemoServiceImpl.savePersonWithoutRollBack(DemoServiceImpl.java:30) ~[classes/:na]
    at com.wyk.datarestdemo.service.impl.DemoServiceImpl$$FastClassBySpringCGLIB$$2ef6f418.invoke(<generated>) ~[classes/:na]
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    '''

查看数据库,发现语句已经插入成功。

mysql> select * from t_person;
+----+----------+------+----------+
| id | name     | age  | address  |
+----+----------+------+----------+
|  1 | xiaoming |   22 | Beijing  |
|  2 | xiaohong |   21 | Beijing  |
|  3 | Peter    |   18 | New York |
|  4 | Jingjing |   18 | Hengshui |
|  5 | Lily     |   28 | Tianjin  |
|  6 | ss       |   25 | Shanghai |
| 10 | wyk      |   29 | NULL     |
+----+----------+------+----------+
7 rows in set (0.00 sec)

Spring 数据缓存

缓存是实际工作中非经常常使用的一种提高性能的方法, 我们会在很多场景下来使用缓存。

Spring 缓存支持

Spring 定义了 org.springframework.cache.CacheMananger 和 org.springframework.cache.Cache 接口用来统一不同的缓存技术。其中,CacheManager 是 Spring 提供的各种缓存技术的抽象接口, Cache 接口包含缓存的各种操作(一般不直接使用)。

Spring 支持的 CacheManager

针对不同的缓存技术,需要实现不同的 CacheManger,Spring 定义了多个 CacheManager 实现。

CacheManager 描述
SimpleCacheManager 使用简单的 Collection 来存储缓存,主要用来测试用途
ConcurrentMapCacheManager 使用 ConcurrentMap 来存储缓存
NoOpCacheManager 仅测试用途,不会实际存储缓存
EhCacheCacheManager 使用 EhCache 作为缓存技术
GuavaCacheManger 使用 Google Guava 的 GuavaCache 作为缓存技术
HazelcastCacheManager 使用 Hazelcast 作为缓存
JCacheCacheManager 使用 JCache 标准的实现作为缓存技术
RedisCacheManager 使用Redis作为缓存技术

在我们使用任意一个实现的 CacheManager 的时候,需注册实现 CacheManager 的 Bean。

@Bean
public EhCacheCacheManager cacheManager(CacheManager ehCacheCacheManager) {
    return new EhCacheCacheManager(ehCacheCacheManager);
}

声明式缓存注解

Spring 提供了4个注解来声明缓存规则。

注解 作用
@Cacheable 方法执行前,先从缓存中读取数据,如果缓存没有找到数据,再调用方法获取数据,然后把数据添加到缓存中
@CachePut 调用方法时会自动把方法返回的相应数据放入缓存
@CacheEvict 调用方法时会从缓存中移除相应的数据
@Caching 组合多个注解策略在一个方法上

@Cacheable、@CachePut、@CacheEvit 都有 value 属性,指定缓存名称,key 属性指定的是数据在缓存中的存储的键。

开启声明式缓存支持

在配置类上使用 @EnableCaching 即可开启声明式缓存支持。

@Condiguration
@EnableCaching
public class AppConfig {

}

Spring Boot 缓存支持

在 Spring Boot 中已经自动配置了多个 CacheManager 的实现。在不做任何额外配置的情况下默认使用 SimpleCacheManager。Spring Boot 支持以 spring.cache 为前缀的属性来配置缓存。

spring.cache.type= # 缓存类型
spring.cache.cache-names= # 程序启动时创建缓存名称
spring.cache.ehcache.config= # ehcache配置文件地址
spring.cache.hazelcast.config= # hazelcast配置文件地址
spring.cache.jcache.provider= # 当多个 jcache 实现在类路径的时候,指定 jcache 实现
spring.cache.guava.spec= # guava specs

在 Spring Boot 环境下,只需要在项目中导入相关缓存技术的依赖包,并在配置类上使用 @EnableConfig 开启缓存支持即可。

新建 Spring Boot 项目,添加依赖至 pom.xml 。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

在配置文件 application.properties 中添加数据库配置信息。

# 数据库相关
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/springstudy
spring.datasource.username=spring
spring.datasource.password=spring
spring.datasource.dirverClassName=com.mysql.jdbc.Driver

创建和前面相同的实体类。

package com.wyk.cachedemo.bean;

import javax.persistence.*;

@Entity
@Table(name = "t_person")
public class Person {
    @Id //映射为数据库主键
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 生成方式为自增
    private Long id;
    private String name;
    private Integer age;
    private String address;

    public Person() {}

    public Person(Long id, String name, Integer age, String address) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.address = address;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

添加实体类的 Repository 。

package com.wyk.cachedemo.repository;

import com.wyk.cachedemo.bean.Person;
import org.springframework.data.jpa.repository.JpaRepository;

public interface PersonRepository extends JpaRepository<Person, Long> {
}

添加业务服务接口:

package com.wyk.cachedemo.service;

import com.wyk.cachedemo.bean.Person;

public interface DemoService {
    public Person save(Person person);

    public void remove(Long id);

    public Person findOne(Person person);
}

添加业务服务实现:

package com.wyk.cachedemo.service.impl;

import com.wyk.cachedemo.repository.PersonRepository;
import com.wyk.cachedemo.bean.Person;
import com.wyk.cachedemo.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Service
public class DemoServiceImpl implements DemoService {

    @Autowired
    PersonRepository personRepository;

    @Override
    @CachePut(value="people", key="#person.id")
    public Person save(Person person) {
        Person p = personRepository.save(person);
        System.out.println("为 id、key为"  + p.getId() + "数据做了缓存");
        return p;
    }

    @Override
    @CacheEvict(value="people")
    public void remove(Long id) {
        System.out.println("删除了id、key为" + id + "的数据库缓存");
        personRepository.deleteById(id);
    }

    @Override
    @Cacheable(value="people", key="#person.id")
    public Person findOne(Person person) {
        Optional<Person> p = personRepository.findById(person.getId());
        System.out.println("为 id、key为"  + person.getId() + "数据做了缓存");
        if(p.isPresent()) {
            return p.get();
        } else {
            return null;
        }

    }
}

添加控制器:

package com.wyk.cachedemo.controller;

import com.wyk.cachedemo.bean.Person;
import com.wyk.cachedemo.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CacheController {
    @Autowired
    DemoService demoService;

    @RequestMapping("/put")
    public Person put(Person person) {
        return demoService.save(person);
    }

    @RequestMapping("/able")
    public Person cacheable(Person person) {
        return demoService.findOne(person);
    }

    @RequestMapping("/evit")
    public String evit(Long id) {
        demoService.remove(id);
        return "ok";
    }
}

开启缓存支持。

package com.wyk.cachedemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching
public class CacheDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(CacheDemoApplication.class, args);
    }

}

运行程序。首先访问 http://localhost:8080/able?id=1

springboot-jpa-save.png

还可以访问 http://localhost:8080/q1?name=wykhttp://localhost:8080/q2?age=30 查看查询结果,这里不再演示。

Redis

待补充。

相关文章

网友评论

      本文标题:Spring Boot学习笔记(四):Spring Boot 数

      本文链接:https://www.haomeiwen.com/subject/mozdvqtx.html