spring-data-jpa 中文文檔(2)

spring-data-jpa 中文文檔(2)

  • JPA Repositories

    • 簡介

      • Spring命名空間
        SpringData使用了自定義的命名空間去定義repository。通常我們會使用repositories元素:

            <?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:jpa="http://www.springframework.org/schema/data/jpa"
              xsi:schemaLocation="http://www.springframework.org/schema/beans
                http://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/data/jpa
                http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
        
              <jpa:repositories base-package="com.acme.repositories" />
            </beans>

        這個配置中啓用了持久化異常處理,所有標誌了@Repository的Bean將會被轉換成爲Spring的DataAccessException

        • 自定義命名空間屬性
          除了repositories,JPA命名空間還提供了其他的屬性去控制:

          屬性名 屬性值
          entity-manager-factory-ref 默認的話,是使用ApplicationContext中找到的EntityManagerFactory,如果有多個的時候,則需要特別指明這個屬性,他將會對repositories路徑中找到的類進行處理
          transaction-manager-ref 默認使用系統定義的PlatformTransactionManager,如果有多個事務管理器的話,則需特別指定。
      • 基於註解的配置
        SpringData JPA支持JavaConfig方式的配置:

        @Configuration
        @EnableJpaRepositories
        @EnableTransactionManagement
        class ApplicationConfig {
        
          @Bean
          public DataSource dataSource() {
        
            EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
            return builder.setType(EmbeddedDatabaseType.HSQL).build();
          }
        
          @Bean
          public EntityManagerFactory entityManagerFactory() {
        
            HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
            vendorAdapter.setGenerateDdl(true);
        
            LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
            factory.setJpaVendorAdapter(vendorAdapter);
            factory.setPackagesToScan("com.acme.domain");
            factory.setDataSource(dataSource());
            factory.afterPropertiesSet();
        
            return factory.getObject();
          }
        
          @Bean
          public PlatformTransactionManager transactionManager() {
        
            JpaTransactionManager txManager = new JpaTransactionManager();
            txManager.setEntityManagerFactory(entityManagerFactory());
            return txManager;
          }
        }

        上面的配置中,我們設置了一個內嵌的HSQL數據庫,我們也配置了EntityManagerFactory,並且使用Hibernate作爲持久層。最後也定義了JPATransactionManager。最上面我們還使用了@EnableJpaRepositories註解。

    • 持久化實體

      • 保存實體
        保存實體,我們之前使用了CrudRepository.save(…)方法。他會使用相關的JPA EntityManager來調用persist或者merge,如果數據沒存在於數據庫中,則調用entityManager.persist(..),否
        則調用entityManager.merge(…)。

        • 實體狀態監測策略
          SpringData JPA提供三種策略去監測實體是否存在:

          屬性名 屬性值
          Id-Property inspection (default) 默認的會通過ID來監測是否新數據,如果ID屬性是空的,則認爲是新數據,反則認爲舊數據
          Implementing Persistable 如果實體實現了Persistable接口,那麼就會通過isNew的方法來監測。
          Implementing EntityInformation 這個是很少用的
    • 查詢方法

      • 查詢策略
        你可以寫一個語句或者從方法名中查詢。

        • 聲明查詢方法
          雖然說方法名查詢的方式很方便,可是你可能會遇到方法名查詢規則不支持你所要查詢的關鍵字或者方法名寫的很長,不方便,或者很醜陋。那麼你就需要通過命名查詢或者在方法上使用
          @Query來解決。
        • 查詢創建
          通常我們可以使用方法名來解析查詢語句,例如:

          public interface UserRepository extends Repository<User, Long> {
          
            List<User> findByEmailAddressAndLastname(String emailAddress, String lastname);
          }

          其所支持的在方法名中可以使用的關鍵字:

          關鍵字 例子 JPQL片段
          And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2
          Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2
          Is,Equals findByFirstname,findByFirstnameIs,findByFirstnameEquals … where x.firstname = ?1
          Between findByStartDateBetween … where x.startDate between ?1 and ?2
          LessThan findByAgeLessThan … where x.age < ?1
          LessThanEqual findByAgeLessThanEqual … where x.age ⇐ ?1
          GreaterThan findByAgeGreaterThan … where x.age > ?1
          GreaterThanEqual findByAgeGreaterThanEqual … where x.age >= ?1
          After findByStartDateAfter … where x.startDate > ?1
          Before findByStartDateBefore … where x.startDate < ?1
          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)

          InNotIn也可以用Collection的子類.

    • 使用JPA命名查詢

      • 註解方式

        @Entity
        @NamedQuery(name = "User.findByEmailAddress",
          query = "select u from User u where u.emailAddress = ?1")
        public class User {
        
        }
      • XML方式 略.

      • 聲明接口
        要使用上面的命名查詢,我們的接口需要這麼聲明

        public interface UserRepository extends JpaRepository<User, Long> {
        
          List<User> findByLastname(String lastname);
        
          User findByEmailAddress(String emailAddress);
        }

        SpringData會先從域類中查詢配置,根據”.(原點)“區分方法名,而不會使用自動方法名解析的方式去創建查詢。

    • 使用@Query
      命名查詢適合用於小數量的查詢,我們可以使用@Query來替代:

      public interface UserRepository extends JpaRepository<User, Long> {
      
        @Query("select u from User u where u.emailAddress = ?1")
        User findByEmailAddress(String emailAddress);
      }

      在表達式中使用Like查詢,例子如下:

      public interface UserRepository extends JpaRepository<User, Long> {
      +
        @Query("select u from User u where u.firstname like %?1")
        List<User> findByFirstnameEndsWith(String firstname);
      }

      這個例子中,我們使用了%,當然,你的參數就沒必要加入這個符號了。

      使用原生sql查詢
      我們可以在@Query中使用本地查詢,當然,你需要設置nativeQuery=true,必須說明的是,這樣的話,就不再支持分頁以及排序。

      public interface UserRepository extends JpaRepository<User, Long> {
      
        @Query(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1", nativeQuery = true)
        User findByEmailAddress(String emailAddress);
      }
    • 使用命名參數
      使用命名查詢,我們需要用到@Param來註釋到指定的參數上,如下:

      public interface UserRepository extends JpaRepository<User, Long> {
      
        @Query("select u from User u where u.firstname = :firstname or u.lastname = :lastname")
        User findByLastnameOrFirstname(@Param("lastname") String lastname,
                                       @Param("firstname") String firstname);
      }
    • 使用SpEL表達式
      在Spring Data JPA 1.4以後,我們支持在@Query中使用SpEL表達式(簡介)來接收變量。
      SpEL支持的變量:

      變量名 使用方式 描述
      entityName select x from #{#entityName} x 根據給定的Repository自動插入相關的entityName。有兩種方式能被解析出來:如果域類型定義了@Entity屬性名稱。或者直接使用類名稱。

      以下的例子中,我們在查詢語句中插入表達式(你也可以用@Entity(name = “MyUser”)。

      @Entity
      public class User {
        @Id
        @GeneratedValue
        Long id;
        String lastname;
      }
      public interface UserRepository extends JpaRepository<User,Long> {
        @Query("select u from #{#entityName} u where u.lastname = ?1")
        List<User> findByLastname(String lastname);
      }

      如果你想寫一個通用的Repository接口,那麼你也可以用這個表達式來處理:

      
          @MappedSuperclass
          public abstract class AbstractMappedType {
            …
            String attribute
          }
      
          @Entity
          public class ConcreteType extends AbstractMappedType { … }
      
          @NoRepositoryBean
          public interface MappedTypeRepository<T extends AbstractMappedType>
            extends Repository<T, Long> {
      
            @Query("select t from #{#entityName} t where t.attribute = ?1")
            List<T> findAllByAttribute(String attribute);
          }
      
          public interface ConcreteRepository
            extends MappedTypeRepository<ConcreteType> { … }
    • 修改語句
      之前我們演示瞭如何去聲明查詢語句,當然我們還有修改語句。修改語句的實現,我們只需要在加多一個註解@Modifying:

      @Modifying
      @Query("update User u set u.firstname = ?1 where u.lastname = ?2")
      int setFixedFirstnameFor(String firstname, String lastname);

      這樣一來,我們就使用了update操作來代替select操作。當我們發起update操作後,可能會有一些過期的數據產生,我們不需要自動去清除它們,因爲EntityManager會有效的丟掉那些未提
      交的變更,如果你想EntityManager自動清除,那麼你可以在@Modify上添加clearAutomatically屬性(true);

    • 使用QueryHints(查詢提示 QueryHits簡介)

      public interface UserRepository extends Repository<User, Long> {
      
        @QueryHints(value = { @QueryHint(name = "name", value = "value")},
                    forCounting = false)
        Page<User> findByLastname(String lastname, Pageable pageable);
      }
    • 配置獲取和負載圖(loadgraph) @NamedEntityGraph簡介
      JPA2.1 支持 通過@EntityGraph及其子類型@NamedEntityGraph來定義獲取和負載.它們可以被直接在實體類上,用來配置查詢結果的獲取計劃.獲取的方式(獲取/負載)可以通過@EntityGraphtype屬性來進行配置.
      在一個實體類上定義 named entity graph

      @Entity
      @NamedEntityGraph(name = "GroupInfo.detail",
        attributeNodes = @NamedAttributeNode("members"))
      public class GroupInfo {
      
        // default fetch mode is lazy.
        @ManyToMany
        List<GroupMember> members = new ArrayList<GroupMember>();
      
        …
      }

      在repository接口中引用在實體類上定義的named entity graph

      @Repository
      public interface GroupRepository extends CrudRepository<GroupInfo, String> {
      
        @EntityGraph(value = "GroupInfo.detail", type = EntityGraphType.LOAD)
        GroupInfo getByGroupName(String name);

      它也可以通過@EntityGraph註解來直接點對點的指定entity graphs.假如依照EntityGraph attributePaths可以被正確的找到,就可以不用在實體類上寫@NamedEntityGraph註解了:

      @Repository
      public interface GroupRepository extends CrudRepository<GroupInfo, String> {
      
        @EntityGraph(attributePaths = { "members" })
        GroupInfo getByGroupName(String name);
      
      }
    • 投影(Projections)
      通常情況下 Spring Data Repositories 會返回整個domain 類.有時候,你需要因爲不同的原因,修改domain類的返回結果.
      看下面的例子:

      @Entity
      public class Person {
      
        @Id @GeneratedValue
        private Long id;
        private String firstName, lastName;
      
        @OneToOne
        private Address address;
        …
      }
      
      @Entity
      public class Address {
      
        @Id @GeneratedValue
        private Long id;
        private String street, state, country;
      
        …
      }

      Person的幾個屬性:

      • id是主鍵
      • fristNamelastName是數據屬性.
      • address鏈接到其他的domain類型.
        現在假設我們創建了一個像下邊這樣的repository:
        interface PersonRepository extends CrudRepository<Person, Long> {
          Person findPersonByFirstName(String firstName);
        }

      Spring Data將會返回domain類的所有屬性.現在有兩種選擇去僅僅返回address屬性:

      • Address定義一個repository:
        interface AddressRepository extends CrudRepository<Address, Long> {}

      在這種情況下 用PersonRepository將會返回整個Person對象.使用AddressRepository僅僅會返回Address對象.
      但是,如果你真的不想暴露address的信息怎麼辦?你可以提供一個像下邊這樣的repository,僅僅提供你想暴露的屬性:

      interface NoAddresses {  
      
        String getFirstName(); 
      
        String getLastName();  
      }

      其中

      • interface NoAddresses定義一個接口
      • String getFirstName();導出firstName
      • String getLastName();導出lastName

      這個NoAddress 只有firstNamelastName的getter方法.它意味着它將不會提供任何的address信息.在定義查詢方法的時候,應該用NoAddress代替Person

      interface PersonRepository extends CrudRepository<Person, Long> {
      
        NoAddresses findByFirstName(String firstName);
      }
      • 改變屬性的值
        現在你已經知道了怎麼樣對結果進行投影,已達到不暴露不必要的部分給用戶.投影也可以調整要暴露的數據模型.你可以添加一個虛擬的屬性:

        interface RenamedProperty {    
        
          String getFirstName();       
        
          @Value("#{target.lastName}")
          String getName();            
        }

        其中:

        • interface RenamedProperty 定義一個接口.
        • String getFirstName();導出firstName屬性.
        • @Value("#{target.lastName}") String getName();導出name屬性,由於此屬性是虛擬的,因此他需要用`@Value("#{target.lastName}")來指定數據的來源.

        如果你想獲得一個人的全稱.你可能要用String.format("%s %s", person.getFirstName(), person.getLastName())來拼接.用虛擬的屬性可以這樣來實現:

        interface FullNameAndCountry {
        
          @Value("#{target.firstName} #{target.lastName}")
          String getFullName();
        
          @Value("#{target.address.country}")
          String getCountry();
        }

        實際上@Value可以完全訪問對象及其內嵌的屬性.SpEL表達式對於施加在投影方法上的定義來說也是非常強大的:
        想想你有一個如下的domain模型:

        @Entity
        public class User {
        
          @Id @GeneratedValue
          private Long id;
          private String name;
        
          private String password;
          …
        }

        在某些情況下,你想讓密碼在可能的情況下不讓其明文出現.這種情況下你可以用@ValueSpEL表達式來創建一個投影:

        interface PasswordProjection {
          @Value("#{(target.password == null || target.password.empty) ? null : '******'}")
          String getPassword();
        }

        這個表達式判斷當password是null或者empty的時候返回null,否則返回’******’

    • 存儲過程
      JPA2.1 規格介紹了JPA標準查詢API對存儲過程調用的支持.下面介紹下@Procedure註解的使用.

      DROP procedure IF EXISTS plus1inout
      CREATE procedure plus1inout (IN arg int, OUT res int)
      BEGIN ATOMIC
       set res = arg + 1;
      END

      存儲過程的元數據可以在實體類上通過@NamedStoredProcedureQuery進行配置.

      @Entity
      @NamedStoredProcedureQuery(name = "User.plus1", procedureName = "plus1inout", parameters = {
        @StoredProcedureParameter(mode = ParameterMode.IN, name = "arg", type = Integer.class),
        @StoredProcedureParameter(mode = ParameterMode.OUT, name = "res", type = Integer.class) })
      public class User {}

      在repository 方法上可以通過多種方式引用存儲過程.存儲過程可以直接通過@Procedure註解的value或者procedureName屬性調用或者通過name屬性.如果repository方法沒有名字,則將其作爲後備.

      @Procedure("plus1inout")
      Integer explicitlyNamedPlus1inout(Integer arg);
      
      @Procedure(procedureName = "plus1inout")
      Integer plus1inout(Integer arg);
      
      @Procedure(name = "User.plus1IO")
      Integer entityAnnotatedCustomNamedProcedurePlus1IO(@Param("arg") Integer arg);
      
      @Procedure
      Integer plus1(@Param("arg") Integer arg);
    • JPA2 引入了criteria API 去建立查詢,Spring Data JPA使用Specifications來實現這個API。在Repository中,你需要繼承JpaSpecificationExecutor:

      public interface CustomerRepository extends CrudRepository<Customer, Long>, JpaSpecificationExecutor {
       …
      }

      下面先給個例子,演示如何利用findAll方法返回所有符合條件的對象:

      List<T> findAll(Specification<T> spec);

      Specification 接口定義如下:

      public interface Specification<T> {
        Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
                  CriteriaBuilder builder);
      }

      好了,那麼我們如何去實現這個接口呢?代碼如下:

      public class CustomerSpecs {
      
        public static Specification<Customer> isLongTermCustomer() {
          return new Specification<Customer>() {
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query,
                  CriteriaBuilder builder) {
      
               LocalDate date = new LocalDate().minusYears(2);
               return builder.lessThan(root.get(_Customer.createdAt), date);
            }
          };
        }
      
        public static Specification<Customer> hasSalesOfMoreThan(MontaryAmount value) {
          return new Specification<Customer>() {
            public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
                  CriteriaBuilder builder) {
      
               // build query here
            }
          };
        }
      }

      好了,那如果有多個需要結合的話,我們可以這麼做:

      MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR);
      List<Customer> customers = customerRepository.findAll(
        where(isLongTermCustomer()).or(hasSalesOfMoreThan(amount)));
    • Query by Example

      • 介紹
        這一部分介紹Query by Example和解釋怎麼去使用Examples.
      • 使用
        Query by Example由三部分組成.

        • Probe 這是填充字段的domain對象的實際範例.
        • ExampleMatcher ExampleMatcher 描述怎麼去匹配特定字段的細節.他可以通過多個Examples進行重複利用.
        • Example 它由ExampleMatcherProbe組成.用來創建一個查詢.

        Query by Example使用與多種情況,但是它也有一些限制.
        適用於:

        • 用靜態或者靜態的約束來查詢數據
        • domain對象的頻繁重構,而不用擔心破壞現有的查詢
        • 根據底層數據存儲API進行獨立工作
          限制:
        • AND關鍵字進行條件查詢的時候.
        • 不支持內嵌的/組合的屬性約束,像firstname = ?0 or (firstname = ?1 and lastname = ?2)
        • 僅僅支持starts/contains/ends/regex匹配strings或者精確匹配其他屬性類型.
          在使用Query by Example之前,你需要有一個domain對象:

          public class Person {
          
            @Id
            private String id;
            private String firstname;
            private String lastname;
            private Address address;
          
            // … getters and setters omitted
          }

          這是一個簡單的domain對象.你可以使用它去創建一個Example默認情況下,字段爲null值時會被忽略.
          Examples通過使用of工廠方法或者ExampleMatcher來創建.Example是不可改變的.

          Person person = new Person();                         
          person.setFirstname("Dave");                          
          
          Example<Person> example = Example.of(person);         

          其中:

          • Person person = new Person();創建一個domain對象實例
          • person.setFirstname("Dave");設置firstName屬性去查詢
          • Example<Person> example = Example.of(person); 創建Example
            Examples用repositories來執行是非常理想的.要使用它需要讓你的repository接口繼承QueryByExampleExecutor<T>.
            public interface QueryByExampleExecutor<T> {
            
              <S extends T> S findOne(Example<S> example);
            
              <S extends T> Iterable<S> findAll(Example<S> example);
            
              // … more functionality omitted.
            }
        • Example matchers
          Example不僅僅侷限於默認的設置.你可以給strings定義自己的默認值然後去匹配.使用ExampleMatcher綁定null和特定屬性的設置.

          Person person = new Person();                          
          person.setFirstname("Dave");                           
          
          ExampleMatcher matcher = ExampleMatcher.matching()     
            .withIgnorePaths("lastname")                         
            .withIncludeNullValues()                             
            .withStringMatcherEnding();                          
          
          Example<Person> example = Example.of(person, matcher); 

          其中:

          • Person person = new Person(); 創建一個domain對象實例.
          • 設置屬性值去查詢.
          • ExampleMatcher matcher = ExampleMatcher.matching()創建一個ExampleMatcher讓其可以使用,但沒有多餘的配置項.
          • .withIgnorePaths("lastname") Construct a new ExampleMatcher to ignore the property path lastname.
          • .withIncludeNullValues() Construct a new ExampleMatcher to ignore the property path lastname and to include null values.
          • .withStringMatcherEnding(); Construct a new ExampleMatcher to ignore the property path lastname, to include null values, and use perform suffix string matching.
          • Example<Person> example = Example.of(person, matcher);根據domain對象和配置的ExampleMatcher對象來創建一個Example

          你可以給個別的屬性指定行爲.(比如.firstnamelastname以及domain對象的嵌套屬性address.city)
          你可以調整他讓他匹配大小寫敏感的選項.

          ExampleMatcher matcher = ExampleMatcher.matching()
            .withMatcher("firstname", endsWith())
            .withMatcher("lastname", startsWith().ignoreCase());
          }

          另一種配置matcher選項的方式是通過使用Java 8 lambdas表達式.這種方式是一種可以通過詢問實現者修改matcher的回調方法.你不需要去返回matcher,因爲他已經保留了matcher的實例.(引用)

          ExampleMatcher matcher = ExampleMatcher.matching()
            .withMatcher("firstname", match -> match.endsWith())
            .withMatcher("firstname", match -> match.startsWith());
          }

          ExampleMatcher可以設置的範圍

          設置 範圍
          Null-handling ExampleMatcher
          String matching ExampleMatcher and property path
          Ignoring properties Property path
          Case sensitivity ExampleMatcher and property path
          Value transformation Property path

          Property path指 這個設置項要跟在需要設置的屬性後邊,而不是在ExampleMatcher對象上進行設置.
          對比ExampleMatcher matcher = ExampleMatcher.matching().withMatcher("firstname", endsWith()).withMatcher("lastname", startsWith().ignoreCase());ExampleMatcher matcher = ExampleMatcher.matching().withIgnorePaths("lastname").withIncludeNullValues().withStringMatcherEnding();

        • 執行一個Example

          public interface PersonRepository extends JpaRepository<Person, String> { … }
          
          public class PersonService {
          
            @Autowired PersonRepository personRepository;
          
            public List<Person> findPeople(Person probe) {
              return personRepository.findAll(Example.of(probe));
            }
          }

          僅僅只有SingularAttribute(單數屬性)可以被屬性匹配正確使用.
          StringMatcher選項:

          匹配 邏輯結果
          DEFAULT (case-sensitive) firstname = ?0
          DEFAULT (case-insensitive) LOWER(firstname) = LOWER(?0)
          EXACT (case-sensitive) firstname = ?0
          STARTING (case-sensitive) firstname like ?0 + ‘%’
          STARTING (case-insensitive) LOWER(firstname) like LOWER(?0) + ‘%’
          ENDING (case-sensitive) firstname like ‘%’ + ?0
          ENDING (case-insensitive) LOWER(firstname) like ‘%’ + LOWER(?0)
          CONTAINING (case-sensitive) firstname like ‘%’ + ?0 + ‘%’
          CONTAINING (case-insensitive) LOWER(firstname) like ‘%’ + LOWER(?0) + ‘%’
    • 事務
      默認的CRUD操作在Repository裏面都是事務性的。如果是隻讀操作,只需要設置事務readOnly爲true,其他的操作則配置爲@Transaction。如果你想修改一個Repository的事務性,你只需要在子接口中重寫並且修改他的事務:

      public interface UserRepository extends CrudRepository<User, Long> {
            @Override
            @Transactional(timeout = 10)
            public List\<\User\>\ findAll();
            // Further query method declarations
      }

      這樣會讓findAll方法在10秒內執行否則會超時的非只讀事務中。
      另一種修改事務行爲的方式在於使用門面或者服務層中,他們包含了多個repository。

      @Service
      class UserManagementImpl implements UserManagement {
      private final UserRepository userRepository;
      private final RoleRepository roleRepository;
      @Autowired
      public UserManagementImpl(UserRepository userRepository,
        RoleRepository roleRepository) {
        this.userRepository = userRepository;
        this.roleRepository = roleRepository;
      }
      @Transactional
      public void addRoleToAllUsers(String roleName) {
        Role role = roleRepository.findByName(roleName);
        for (User user : userRepository.findAll()) {
          user.addRole(role);
          userRepository.save(user);
        }
      }

      這將會導致在調用addRoleToAllUsers方法的時候,創建一個或者加入一個事務中去。實際在Repository裏面定義的事務將會被忽略,而外部定義的事務將會被應用。當然,要使用事務,你需要聲明(這個例子中,假設你已經使用了component-scan)
      要讓方法在事務中,最簡單的方式就是使用@Transactional註解:

      @Transactional(readOnly = true)
      public interface UserRepository extends JpaRepository<User, Long> {
      List <User> findByLastname(String lastname);
      @Modifying
      @Transactional
      @Query("delete from User u where u.active = false")
      void deleteInactiveUsers();
      }

      一般的查詢操作,你需要設置readOnly=true。在deleteInactiveUsers方法中,我們添加了Modifying註解以及覆蓋了Transactional,這樣這個方法執行的時候readOnly=false了.


    • 想要爲方法指定鎖的類型,你只需要使用@Lock:

      interface UserRepository extends Repository<User, Long> {
      // Plain query method
      @Lock(LockModeType.READ)
      List<User> findByLastname(String lastname);
      }

      當然你也可以覆蓋原有的方法:

      interface UserRepository extends Repository<User, Long> {
        // Redeclaration of a CRUD method
        @Lock(LockModeType.READ);
        List<User> findAll();
      }
    • 審計

      • 基礎知識
        SpringData爲您跟蹤誰創建或者修改數據,以及相應的時間提供了複雜的支持。你現在想要這些支持的話,僅僅需要使用幾個註解或者實現接口即可。

        • 註解方式: 我們提供了@CreatedBy, @LastModifiedBy去捕獲誰操作的實體,當然還有相應的時間@CreatedDate@LastModifiedDate

          class Customer {
          
            @CreatedBy
            private User user;
          
            @CreatedDate
            private DateTime createdDate;
          
            // … further properties omitted
          }

          正如你看到的,你可以選擇性的使用這些註解。操作時間方面,你可以使用org.joda.time.DateTime 或者java.util.Date或者long/Long表示。

        • 基於接口的審計:
          如果你不想用註解來做審計的話,那麼你可以實現Auditable接口。他暴露了審計屬性的get/set方法。
          如果你不想實現接口,那麼你可以繼承AbstractAuditable,通常來說,註解方式時更加方便的。

        • 審計織入:
          如果你在用@CreatedBy或者@LastModifiedBy的時候,想織入當前的業務操作者,那你可以使用我們提供的AuditorAware接口。T表示你想織入在這兩個字段上的類型。

          class SpringSecurityAuditorAware implements AuditorAware<User> {
          
            public User getCurrentAuditor() {
          
              Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
          
              if (authentication == null || !authentication.isAuthenticated()) {
                return null;
              }
          
              return ((MyUserDetails) authentication.getPrincipal()).getUser();
            }
          }
          在這個實現類中,我們使用SpringSecurity內置的Authentication來查找用戶的UserDetails。
          
        • 通用審計配置:
          SpringData JPA有一個實體監聽器,他可以用於觸發捕獲審計信息。要用之前,你需要在orm.xml裏面註冊AuditingEntityListener.當然你得引入spring-aspects.jar
          審計配置:

          <persistence-unit-metadata>
          <persistence-unit-defaults>
          <entity-listeners>
            <entity-listener class="….data.jpa.domain.support.AuditingEntityListener" />
          </entity-listeners>
          </persistence-unit-defaults>
          </persistence-unit-metadata>

          你也可以再每個實體類上使用@EntityListeners註解來激活AuditingEntityListener監聽.

          @Entity
          @EntityListeners(AuditingEntityListener.class)
          public class MyEntity {
          
          }

          要啓用這個審計,我們還需要在配置文件裏面配置多一條:

          <jpa:auditing auditor-aware-ref="yourAuditorAwareBean" />

          Spring Data JPA 1.5之後,你也可以使用@EnableJpaAuditing註解來激活.

          @Configuration
          @EnableJpaAuditing
          class Config {
          
            @Bean
            public AuditorAware<AuditableUser> auditorProvider() {
              return new AuditorAwareImpl();
            }
          }
    • 其他: 略. 點擊查看

    • 附錄

      • 附錄A: 命名空間引用
        <repositories />的元素:

        屬性名稱 描述
        base-package 定義去掃描哪些繼承了*Repository接口的用戶自定義Repository接口.
        repository-impl-postfix 定義用戶自定義實現sql語句的實現類以什麼結尾,以用來自動發現.默認是Impl
        query-lookup-strategy 定義查詢的策略.默認的是create-if-not-found
        named-queries-location 定義去哪裏尋找已經寫好了named-query查詢的配置文件
        consider-nested-repositories 考慮是否要控制內嵌的Repository接口.默認是false
      • 附錄B:Populators 命名空間引用
        <populator />的元素:

        屬性名稱 描述
        locations 尋找要填入Repository接口的對象的值的文件.
      • 附錄C:Repository 查詢關鍵詞
        支持的查詢關鍵字:

        Logical keyword Keyword expressions

        AND

        And

        OR

        Or

        AFTER

        After, IsAfter

        BEFORE

        Before, IsBefore

        CONTAINING

        Containing, IsContaining, Contains

        BETWEEN

        Between, IsBetween

        ENDING_WITH

        EndingWith, IsEndingWith, EndsWith

        EXISTS

        Exists

        FALSE

        False, IsFalse

        GREATER_THAN

        GreaterThan, IsGreaterThan

        GREATER_THAN_EQUALS

        GreaterThanEqual, IsGreaterThanEqual

        IN

        In, IsIn

        IS

        Is, Equals, (or no keyword)

        IS_NOT_NULL

        NotNull, IsNotNull

        IS_NULL

        Null, IsNull

        LESS_THAN

        LessThan, IsLessThan

        LESS_THAN_EQUAL

        LessThanEqual, IsLessThanEqual

        LIKE

        Like, IsLike

        NEAR

        Near, IsNear

        NOT

        Not, IsNot

        NOT_IN

        NotIn, IsNotIn

        NOT_LIKE

        NotLike, IsNotLike

        REGEX

        Regex, MatchesRegex, Matches

        STARTING_WITH

        StartingWith, IsStartingWith, StartsWith

        TRUE

        True, IsTrue

        WITHIN

        Within, IsWithin

      • 附錄D:Repository查詢的返回結果類型
        支持的查詢返回結果類型:

        Return type Description

        void

        Denotes no return value.

        Primitives

        Java primitives.

        Wrapper types

        Java wrapper types.

        T

        An unique entity. Expects the query method to return one result at most. In case no result is found null is returned. More than one result will trigger an IncorrectResultSizeDataAccessException.

        Iterator<T>

        An Iterator.

        Collection<T>

        A Collection.

        List<T>

        A List.

        Optional<T>

        A Java 8 or Guava Optional. Expects the query method to return one result at most. In case no result is found Optional.empty()/Optional.absent() is returned. More than one result will trigger an IncorrectResultSizeDataAccessException.

        Stream<T>

        A Java 8 Stream.

        Future<T>

        A Future. Expects method to be annotated with @Async and requires Spring’s asynchronous method execution capability enabled.

        CompletableFuture<T>

        A Java 8 CompletableFuture. Expects method to be annotated with @Async and requires Spring’s asynchronous method execution capability enabled.

        ListenableFuture

        A org.springframework.util.concurrent.ListenableFuture. Expects method to be annotated with @Async and requires Spring’s asynchronous method execution capability enabled.

        Slice

        A sized chunk of data with information whether there is more data available. Requires a Pageable method parameter.

        Page<T>

        A Slice with additional information, e.g. the total number of results. Requires a Pageable method parameter.

        GeoResult<T>

        A result entry with additional information, e.g. distance to a reference location.

        GeoResults<T>

        A list of GeoResult<T> with additional information, e.g. average distance to a reference location.

        GeoPage<T>

        A Page with GeoResult<T>, e.g. average distance to a reference location.

      • 附錄E : faq. 略 點擊查看
      • 附錄F: 詞彙表: 略 點擊查看
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章