1. 程式人生 > >Spring Data Jpa(三)

Spring Data Jpa(三)

Spring Data Jpa介紹

    A、Spring Data JPA 是 Spring 基於 ORM 框架、JPA 規範的基礎上封裝的套 JPA 應用框架,
       可使開發者用極簡的程式碼即可實現對資料的訪問和操作。它提供了包括增刪改查等在內的常用功能,
       且易於擴充套件!學習並使用 Spring Data JPA 可以極大提高開發效率!
    B、Spring Data JPA 簡化了 DAO 層的操作,基本上所有 CRUD 都可以依賴於其實現。

1、入門案例

    A、新增依賴:
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-jpa</artifactId>
            </dependency>   
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>

    B、新增配置檔案:
            # 配置資料庫
            spring.datasource.url=jdbc:mysql://localhost:3306/test
            spring.datasource.username=root
            spring.datasource.password=root
            spring.datasource.driver-class-name=com.mysql.jdbc.Driver           
            # 配置 Jpa 相關引數
            spring.jpa.properties.hibernate.hbm2ddl.auto=update 
            spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
            spring.jpa.show-sql= true
            
            # hibernate.hbm2ddl.auto 引數的作用主要用於:自動建立 | 更新 | 驗證資料庫表結構,有四個值:
            #       create:每次載入 hibernate 時都會刪除上一次的生成的表,然後根據 model 類再重新來生成新表,
            #               哪怕兩次沒有任何改變也要這樣執行,這就是導致資料庫表資料丟失的一個重要原因。
            #       create-drop:每次載入 hibernate 時根據 model 類生成表,但是 sessionFactory 關閉,表就自動刪除。
            #       update:最常用的屬性,第1次載入 hibernate 時根據 model 類會自動建立起表的結構(前提是先建好
            #               資料庫),以後載入 hibernate 時根據 model 類自動更新表結構,即使表結構改變了,但表中的行仍
            #               然存在,不會刪除以前的行。要注意的是當部署到伺服器後,表結構是不會被馬上建立起來的,是要等
            #               應用第一次執行起來後才會。
            #       validate:每次載入 hibernate 時,驗證建立資料庫表結構,只會和資料庫中的表進行比較,不會建立新
            #               表,但是會插入新值。
            # dialect:主要是指定生成表名的儲存引擎為 InneoDB。
            # show-sql:是否打印出自動生產的 SQL,方便除錯的時候檢視。

    C、新增實體類和Dao : Entity 中不對映成列的欄位得加 @Transient 註解,不加註解也會對映成列       

            @Entity
            public class User implements Serializable {
                @Id
                @GeneratedValue
                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 = false)
                private String regTime;

                //省略 getter settet 方法、構造方法
            }

        Dao:
            public interface UserRepository extends JpaRepository<User, Long> {
                User findByUserName(String userName);
                User findByUserNameOrEmail(String username,String email);
            } 

    D、編寫測試類 
            @RunWith(SpringRunner.class)
            @SpringBootTest
            public class UserRepositoryTest {
            
                @Resource
                private UserRepository userRepository;
            
                @Test
                public void findByUserName() throws Exception {
            
                    /**
                     * 資料庫造資料
                     *  Date date=new Date();
                     *  DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG);
                     *  String formatDate = dateFormat.format(date);
                     *  userRepository.save(new User("aa", "aa123456","
[email protected]
",formatDate)); * userRepository.save(new User("bb", "bb123456","[email protected]",formatDate)); * userRepository.save(new User("cc", "cc123456","[email protected]",formatDate)); */ Assert.assertEquals(3,userRepository.findAll().size()); //userRepository.delete(userRepository.findByUserName("aa")); } @Test public void findByUserNameOrEmail() throws Exception { Assert.assertEquals("bb",userRepository.findByUserNameOrEmail("bb","
[email protected]
").getUserName()); } }

2、基本查詢分為兩種,① Spring Data 預設已經實現 ② 根據查詢的方法來自動解析成 SQL。

    A、Spring Data JPA 預設預先生成一些基本的 CURD 的方法,如增、刪、改等。
            繼承 JpaRepository:
                public interface UserRepository extends JpaRepository<User, Long> {

                }

            使用預設方法:
                @Test
                public void testBaseQuery() {
                    userRepository.findAll();
                    userRepository.findOne(1l);
                    userRepository.save(user);
                    userRepository.delete(user);
                    userRepository.count();
                    userRepository.exists(1l);
                    // ...
                }
    B、自定義的簡單查詢就是根據方法名來自動生成 SQL,主要的語法是 findXXBy、readAXXBy、queryXXBy、countXXBy、getXXBy 
        後面跟屬性名稱:
                    User findByUserName(String userName);
        也可加一些關鍵字 And、Or:
                    User findByUserNameOrEmail(String username, String email);
        修改、刪除、統計類似語法:
                    Long deleteById(Long id);
                    Long countByUserName(String userName)
        基本上 SQL 體系中的關鍵詞都可以使用,如 LIKE、IgnoreCase、OrderBy:
                    List<User> findByEmailLike(String email);
                    User findByUserNameIgnoreCase(String userName);  
                    List<User> findByUserNameOrderByEmailDesc(String email);    

3、複雜查詢

在實際的開發中需要用到分頁、篩選、連表等查詢的時候就需要特殊的方法或者自定義 SQL。

    分頁查詢:
        分頁查詢在實際使用中非常普遍,Spring Data JPA 已經幫我們實現了分頁的功能,在查詢的方法中, 
        需要傳入引數 Pageable,當查詢中有多個引數的時候, Pageable 建議做為最後一個引數傳入

        Pageable 是 Spring 封裝的分頁實現類,使用的時候需要傳入頁數、每頁條數和排序規則。

        @Query("select u from User u")
        Page<User> findALL(Pageable pageable);
        Page<User> findByUserName(String userName,Pageable pageable);
        
        測試
        @Test
        public void testPageQuery(){
            int page=1,size=2;
            Sort sort=new Sort(Sort.Direction.DESC,"id");
            Pageable pageable=new PageRequest(page,size,sort);
            userRepository.findALL(pageable);
            userRepository.findByUserName("aa",pageable);
        }

4、自定義SQL查詢

    A、使用 Spring Data 大部分的 SQL 都可以根據方法名定義的方式來實現,如果我們想使用自定義的 
       SQL 來查詢,Spring Data 也可以支援
    B、在 SQL 的查詢方法上面使用 @Query 註解,如涉及到刪除和修改需要加上 @Modifying,也可以
       根據需要新增 @Transactional 對事物的支援,查詢超時的設定等。

        @Transactional(timeout = 10)
        @Modifying
        @Query("update User set userName =?1 where id =?2")
        int modifyById(String userName,Long id);
    
        @Transactional
        @Modifying
        @Query("delete from User where id =?1")
        void deleteById(Long id);
    
        @Query("select u from User u where u.email = ?1")
        User findByEmail(String email);

5、多資料來源的支援(重點)

    專案開發中,常需要在一個專案中使用多個數據源,因此需要配置 Spring Data JPA 對多資料來源的使用,一般分為以下三步:
        a、配置多資料來源
        b、不同源的 repository 放入不同包路徑
        c、宣告不同的包路徑下使用不同的資料來源、事務支援

    0、實體類所在包 com.kid.domain
        @Entity
        public class User implements Serializable {
        
            @Id
            @GeneratedValue
            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;
            
            //getter、setter、構造方法省略...
        }

    A、配置兩個資料來源:
        #primary
        spring.primary.datasource.url=jdbc:mysql://localhost:3306/test1 
        spring.primary.datasource.username=root  
        spring.primary.datasource.password=root  
        spring.primary.datasource.driver-class-name=com.mysql.jdbc.Driver  
        #secondary
        spring.secondary.datasource.url=jdbc:mysql://localhost:3306/test2  
        spring.secondary.datasource.username=root  
        spring.secondary.datasource.password=root  
        spring.secondary.datasource.driver-class-name=com.mysql.jdbc.Driver 
    
    B、讀取兩個配置源,構建兩個資料來源:
        com.kid.config 包下:

            @Configuration
            public class DataSourceConfig {
            
                @Bean(name = "primaryDataSource")
                @Qualifier("primaryDataSource")
                @ConfigurationProperties(prefix="spring.primary.datasource")
                public DataSource primaryDataSource() {
                    return DataSourceBuilder.create().build();
                }
            
                @Bean(name = "secondaryDataSource")
                @Qualifier("secondaryDataSource")
                @Primary
                @ConfigurationProperties(prefix="spring.secondary.datasource")
                public DataSource secondaryDataSource() {
                    return DataSourceBuilder.create().build();
                }
            }

    C、將資料來源注入到 Factory,配置 repository、domian 的位置,需要設定一個預設的資料來源:
        com.kid.config 包下:
            PrimaryConfig:
                @Configuration
                @EnableTransactionManagement
                @EnableJpaRepositories(
                        entityManagerFactoryRef="entityManagerFactoryPrimary",
                        transactionManagerRef="transactionManagerPrimary",
                        basePackages= { "com.kid.repository.test1" })//設定dao(repo)所在位置
                public class PrimaryConfig {
                    
                    @Autowired
                    private JpaProperties jpaProperties;
                
                    @Autowired
                    @Qualifier("primaryDataSource")
                    private DataSource primaryDataSource;
                
                    @Bean(name = "entityManagerPrimary")
                    @Primary
                    public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
                        return entityManagerFactoryPrimary(builder).getObject().createEntityManager();
                    }
                
                
                    @Bean(name = "entityManagerFactoryPrimary")
                    @Primary
                    public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary (EntityManagerFactoryBuilder builder) {
                        return builder
                                .dataSource(primaryDataSource)
                                .packages("com.kid.domain") //設定實體類所在位置
                                .persistenceUnit("primaryPersistenceUnit")
                                .properties(getVendorProperties())
                                .build();
                    }
                
                    private Map<String, Object> getVendorProperties() {
                        return jpaProperties.getHibernateProperties(new HibernateSettings() );
                    }
                
                
                    @Bean(name = "transactionManagerPrimary")
                    @Primary
                    PlatformTransactionManager transactionManagerPrimary(EntityManagerFactoryBuilder builder) {
                        return new JpaTransactionManager(entityManagerFactoryPrimary(builder).getObject());
                    }
                }

        SecondaryConfig:
            @Configuration
            @EnableTransactionManagement
            @EnableJpaRepositories(
                    entityManagerFactoryRef="entityManagerFactorySecondary",
                    transactionManagerRef="transactionManagerSecondary",
                    basePackages= { "com.kid.repository.test2" })
            public class SecondaryConfig {
                @Autowired
                private JpaProperties jpaProperties;
            
                @Autowired
                @Qualifier("secondaryDataSource")
                private DataSource secondaryDataSource;
            
                @Bean(name = "entityManagerSecondary")
                public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
                    return entityManagerFactorySecondary(builder).getObject().createEntityManager();
                }
            
                @Bean(name = "entityManagerFactorySecondary")
                public LocalContainerEntityManagerFactoryBean entityManagerFactorySecondary (EntityManagerFactoryBuilder builder) {
                    return builder
                            .dataSource(secondaryDataSource)
                            .properties(getVendorProperties())
                            .packages("com.kid.domain")
                            .persistenceUnit("secondaryPersistenceUnit")
                            .build();
                }
            
                private Map<String, Object> getVendorProperties() {
                    return jpaProperties.getHibernateProperties(new HibernateSettings());
                }
            
                @Bean(name = "transactionManagerSecondary")
                PlatformTransactionManager transactionManagerSecondary(EntityManagerFactoryBuilder builder) {
                    return new JpaTransactionManager(entityManagerFactorySecondary(builder).getObject());
                }
            }


    D、配置Dao
    
        com.kid.repository.test1
            public interface UserTest1Repository extends JpaRepository<User, Long> {
                User findByUserName(String userName);
                User findByUserNameOrEmail(String username, String email);
            }

        com.kid.repository.test2
            public interface UserTest2Repository extends JpaRepository<User, Long> {
                User findByUserName(String userName);
                User findByUserNameOrEmail(String username, String email);
            }
    
    E:測試類編寫

        @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","[email protected]", "aa",  formattedDate));
            userTest1Repository.save(new User("bb", "bb123456","[email protected]", "bb",  formattedDate));
            userTest2Repository.save(new User("cc", "cc123456","[email protected]", "cc",  formattedDate));
        }
    
        檢視資料庫會發現 test1 會有兩條資料,test2 有一條。在實際使用中需要哪個資料來源使用 @Resource 注入即可。