初入spring boot(七 )Spring Data JPA

  Spring Data JPA通过提供基于JPA的Repository极大地减少JPA作为数据访问方案的代码量。

1.定义数据访问层

使用Spring Data JPA建立数据访问层十分简单,只需定义一个继承JpaRepository的接口即可,接口如下:

1 @RepositoryRestResource(path = "people")
2 public interface PersonRepository extends JpaRepository<Person, Long> {
3     
4     @RestResource(path = "nameStartsWith", rel = "nameStartsWith")
5     Person findByNameStartsWith(@Param("name")String name);
6 
7 }

继承JpaRepository接口意味着我们默认已经有了下面的数据访问操作方法:

 1 @NoRepositoryBean
 2 public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
 3     List<T> findAll();
 4 
 5     List<T> findAll(Sort var1);
 6 
 7     List<T> findAll(Iterable<ID> var1);
 8 
 9     <S extends T> List<S> save(Iterable<S> var1);
10 
11     void flush();
12 
13     <S extends T> S saveAndFlush(S var1);
14 
15     void deleteInBatch(Iterable<T> var1);
16 
17     void deleteAllInBatch();
18 
19     T getOne(ID var1);
20 
21     <S extends T> List<S> findAll(Example<S> var1);
22 
23     <S extends T> List<S> findAll(Example<S> var1, Sort var2);
24 }

 

2.配置使用Spring Data JPA

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

 1 @Configuration
 2 @EnableJpaRepositories("com.test.dao")
 3 public class JpaConfiguration {
 4     @Bean
 5     public EntityManagerFactory entityManagerFactory(){
 6         //...
 7         return null;
 8     }
 9     
10     //还需配置DataSource、PlatformTransactionManager等相关必须bean
11 }

 

3.定义查询方法

 (1)根据属性名查询

  1)常规查询。根据属性名来定义查询方法

 1 public interface PersonRepository extends CustomRepository<Person, Long> {
 2 
 3     /**
 4      * 通过名字相等查询,参数name
 5      * 相当于JPQL:select p from Person p where p.name=?
 6      */
 7     List<Person> findByName(String name);
 8 
 9     /**
10      * 通过名字like查询,参数为name
11      * 相当于JPQL:select p from Person p where p.name like ?
12      */
13     List<Person> findByNameLike(String name);
14 
15     /**
16      * 通过名字和地址查询,参数为name和address
17      * 相当于JPQL:select p from Person p where p.name = ? and p.address = ?
18      */
19     List<Pserson> findByNameAndAddress(String name,String address);
20 }

  从代码可以看出,这里使用了findBy、like、And这样的关键字。其中findBy可以用find、read、readBy、query、queryBy、get、getBy来代替。

 2)限制结果数量。结果数量是用top和first关键字来实现的:\

 1 public interface PersonRepository extends CustomRepository<Person, Long> {
 2 
 3     /**
 4      * 获取查询条件的前10条数据
 5      * 通过名字相等查询,参数name
 6      * 相当于JPQL:select p from Person p where p.name=?
 7      */
 8     List<Person> findFirst10ByName(String name);
 9 
10     /**
11      * 获取查询条件的前10条数据
12      * 通过名字like查询,参数为name
13      * 相当于JPQL:select p from Person p where p.name like ?
14      */
15     List<Person> findTop10ByNameLike(String name);
16 
17 }

(2)使用JPA的NamedQuery查询

Spring Data JPA支持用JPA的NameQuery来定义查询方法,即一个名称映射一个查询语句。

 1 @Entity 
 2 @NamedQuery(name = "Person.withNameAndAddressNamedQuery",
 3 query = "select p from Person p where p.name=? and address=?")
 4 public class Person {
 5     @Id 
 6     @GeneratedValue 
 7     private Long id;
 8     
 9     private String name;
10     
11     private Integer age;
12     
13     private String address;
14     
15     
16     
17     public Person() {
18         super();
19     }
20 
21     public Long getId() {
22         return id;
23     }
24     public void setId(Long id) {
25         this.id = id;
26     }
27     public String getName() {
28         return name;
29     }
30     public void setName(String name) {
31         this.name = name;
32     }
33     public Integer getAge() {
34         return age;
35     }
36     public void setAge(Integer age) {
37         this.age = age;
38     }
39     public String getAddress() {
40         return address;
41     }
42     public void setAddress(String address) {
43         this.address = address;
44     }
45 
46 
47 }

这时在接口里使用 Person withNameAndAddressNamedQuery(String name,String address); 时是使用的在上面定义的sql语句,而不是根据方法名称查询

(3)使用@Query查询

  1)使用命名参数,使用名称来匹配查询参数。Spring Data JPA还支持@Query注解在接口的方法上实现查询

    @Query("select p from Person p where p.name= :name and p.address= :address")
    Person withNameAndAddressQuery(@Param("name")String name,@Param("address")String address);

  2)使用参数索引

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

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

(4)JPA提供了基于准则查询方式,即Criteria查询。而Spring Data JPA提供了一个Specification(规范)接口让我们可以更方便的构造准则查询,Specification接口定义了一个toPredicate方法用来构造查询条件。

(5)自定义Repository的实现

spring data提供了CrudRepository和PagingAndSortingRepository,spring data JPA也提供了JpaRepository。如果我们想把自己常用的数据库操作封装起来,像JpaRepository一样提供给我们领域类的Repository接口使用,应该怎么做?

  1)定义自定义Repository接口

@NoRepositoryBean
public interface CustomRepository<T, ID extends Serializable>extends JpaRepository<T, ID> ,JpaSpecificationExecutor<T>{
    
    Page<T> findByAuto(T example,Pageable pageable);
 
}
1. @NoRepositoryBean指明当前这个接口不是我们领域类的接口(例如PersonRepository)
2. 我们自定义的Repository实现JpaRepository接口(这里也可以实现PagingAndSortingRepository接口,看具体需求),具备JpaRepository的能力
3. 要定义的数据操作方法在接口中的定义

  2)定义接口实现

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;
    }

    @Override
    public Page<T> findByAuto(T example, Pageable pageable) {
        return findAll(byAuto(entityManager, example),pageable);  //在此处定义数据访问操作
} }

1. 首先要实现CustomRepository接口,继承SimpleJpaRepository类让我们可以使用其提供的方法(例如:findAll)
2. 让数据库操作方法中可以使用entityManager
3. CustomRepositoryImpl的构造函数,需当前处理的领域类类型和entityManager作为构造函数

  3)自定义RepositoryFactoryBean。自定义JpaRepositoryFactoryBean替代默认RepositoryFactoryBean,我们会获得一个RepositoryFactory,RepositoryFactory将会注册我们自定义的Repository的实现

public class CustomRepositoryFactoryBean<T extends JpaRepository<S, ID>, S, ID extends Serializable>
        extends JpaRepositoryFactoryBean<T, S, ID> {// 1

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

    private static class CustomRepositoryFactory extends JpaRepositoryFactory {// 3


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

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

        }

        @Override
        protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {// 5
            return CustomRepositoryImpl.class;
        }
    }
}

1. 自定义RepositoryFactoryBean,继承JpaRepositoryFactoryBean
2.
重写createRepositoryFactory方法,用当前的CustomRepositoryFactory创建实例
3. 创建CustomRepositoryFactory,并继承JpaRepositoryFactory
4. 重写getTargetRepository方法,获得当前自定义的Repository实现
5. 重写getRepositoryBaseClass,获得当前自定义的Repository实现的类型

 4)开启自定义支持使用@EnableJpaRepositories的repositoryFactoryBeanClass来指定FactoryBean即可

@SpringBootApplication
@EnableJpaRepositories(repositoryFactoryBeanClass = CustomRepositoryFactoryBean.class)
public class TestApplication {
    @Autowired
    PersonRepository personRepository;

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

 

 

Spring boot的支持

1. JDBC的自动配置

spring-boot-starter-data-jpa依赖于spring-boot-starter-jdbc,而Spring Boot对JDBC做了一些自动配置。源码放置在org.springframework.boot.autoconfigure.jdbc下,如图

从源码分析可以看出,我们通过“spring.datasource”为前缀的属性自动配置dataSource,Spring Boot自动开启了注解事务的支持(@EnableTransactionManagement);还配置了一个jdbcTemplate。Spring Boot还提供了一个初始化数据的功能:放置在类路径下的schema.sql文件会自动用来初始化表结构;放置在类路径下的data.sql文件会自动用来填充表数据。

/*
 * Copyright 2012-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.autoconfigure.jdbc;

import java.nio.charset.Charset;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import javax.sql.DataSource;

import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

/**
 * Base class for configuration of a data source.
 *
 * @author Dave Syer
 * @author Maciej Walkowiak
 * @author Stephane Nicoll
 * @author Benedikt Ritter
 * @author Eddú Meléndez
 * @since 1.1.0
 */
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties
        implements BeanClassLoaderAware, EnvironmentAware, InitializingBean {

    private ClassLoader classLoader;

    private Environment environment;

    /**
     * Name of the datasource.
     */
    private String name = "testdb";

    /**
     * Generate a random datasource name.
     */
    private boolean generateUniqueName;

    /**
     * Fully qualified name of the connection pool implementation to use. By default, it
     * is auto-detected from the classpath.
     */
    private Class<? extends DataSource> type;

    /**
     * Fully qualified name of the JDBC driver. Auto-detected based on the URL by default.
     */
    private String driverClassName;

    /**
     * JDBC url of the database.
     */
    private String url;

    /**
     * Login user of the database.
     */
    private String username;

    /**
     * Login password of the database.
     */
    private String password;

    /**
     * JNDI location of the datasource. Class, url, username & password are ignored when
     * set.
     */
    private String jndiName;

    /**
     * Populate the database using 'data.sql'.
     */
    private boolean initialize = true;

    /**
     * Platform to use in the schema resource (schema-${platform}.sql).
     */
    private String platform = "all";

    /**
     * Schema (DDL) script resource references.
     */
    private List<String> schema;

    /**
     * User of the database to execute DDL scripts (if different).
     */
    private String schemaUsername;

    /**
     * Password of the database to execute DDL scripts (if different).
     */
    private String schemaPassword;

    /**
     * Data (DML) script resource references.
     */
    private List<String> data;

    /**
     * User of the database to execute DML scripts.
     */
    private String dataUsername;

    /**
     * Password of the database to execute DML scripts.
     */
    private String dataPassword;

    /**
     * Do not stop if an error occurs while initializing the database.
     */
    private boolean continueOnError = false;

    /**
     * Statement separator in SQL initialization scripts.
     */
    private String separator = ";";

    /**
     * SQL scripts encoding.
     */
    private Charset sqlScriptEncoding;

    private EmbeddedDatabaseConnection embeddedDatabaseConnection = EmbeddedDatabaseConnection.NONE;

    private Xa xa = new Xa();

    private String uniqueName;

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        this.embeddedDatabaseConnection = EmbeddedDatabaseConnection
                .get(this.classLoader);
    }

    /**
     * Initialize a {@link DataSourceBuilder} with the state of this instance.
     * @return a {@link DataSourceBuilder} initialized with the customizations defined on
     * this instance
     */
    public DataSourceBuilder initializeDataSourceBuilder() {
        return DataSourceBuilder.create(getClassLoader()).type(getType())
                .driverClassName(determineDriverClassName()).url(determineUrl())
                .username(determineUsername()).password(determinePassword());
    }

    public String getName() {
        return this.name;
    }

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

    public boolean isGenerateUniqueName() {
        return this.generateUniqueName;
    }

    public void setGenerateUniqueName(boolean generateUniqueName) {
        this.generateUniqueName = generateUniqueName;
    }

    public Class<? extends DataSource> getType() {
        return this.type;
    }

    public void setType(Class<? extends DataSource> type) {
        this.type = type;
    }

    /**
     * Return the configured driver or {@code null} if none was configured.
     * @return the configured driver
     * @see #determineDriverClassName()
     */
    public String getDriverClassName() {
        return this.driverClassName;
    }

    public void setDriverClassName(String driverClassName) {
        this.driverClassName = driverClassName;
    }

    /**
     * Determine the driver to use based on this configuration and the environment.
     * @return the driver to use
     * @since 1.4.0
     */
    public String determineDriverClassName() {
        if (StringUtils.hasText(this.driverClassName)) {
            Assert.state(driverClassIsLoadable(),
                    "Cannot load driver class: " + this.driverClassName);
            return this.driverClassName;
        }
        String driverClassName = null;

        if (StringUtils.hasText(this.url)) {
            driverClassName = DatabaseDriver.fromJdbcUrl(this.url).getDriverClassName();
        }

        if (!StringUtils.hasText(driverClassName)) {
            driverClassName = this.embeddedDatabaseConnection.getDriverClassName();
        }

        if (!StringUtils.hasText(driverClassName)) {
            throw new DataSourceBeanCreationException(this.embeddedDatabaseConnection,
                    this.environment, "driver class");
        }
        return driverClassName;
    }

    private boolean driverClassIsLoadable() {
        try {
            ClassUtils.forName(this.driverClassName, null);
            return true;
        }
        catch (UnsupportedClassVersionError ex) {
            // Driver library has been compiled with a later JDK, propagate error
            throw ex;
        }
        catch (Throwable ex) {
            return false;
        }
    }

    /**
     * Return the configured url or {@code null} if none was configured.
     * @return the configured url
     * @see #determineUrl()
     */
    public String getUrl() {
        return this.url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    /**
     * Determine the url to use based on this configuration and the environment.
     * @return the url to use
     * @since 1.4.0
     */
    public String determineUrl() {
        if (StringUtils.hasText(this.url)) {
            return this.url;
        }
        String url = this.embeddedDatabaseConnection.getUrl(determineDatabaseName());
        if (!StringUtils.hasText(url)) {
            throw new DataSourceBeanCreationException(this.embeddedDatabaseConnection,
                    this.environment, "url");
        }
        return url;
    }

    private String determineDatabaseName() {
        if (this.generateUniqueName) {
            if (this.uniqueName == null) {
                this.uniqueName = UUID.randomUUID().toString();
            }
            return this.uniqueName;
        }
        return this.name;
    }

    /**
     * Return the configured username or {@code null} if none was configured.
     * @return the configured username
     * @see #determineUsername()
     */
    public String getUsername() {
        return this.username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    /**
     * Determine the username to use based on this configuration and the environment.
     * @return the username to use
     * @since 1.4.0
     */
    public String determineUsername() {
        if (StringUtils.hasText(this.username)) {
            return this.username;
        }
        if (EmbeddedDatabaseConnection.isEmbedded(determineDriverClassName())) {
            return "sa";
        }
        return null;
    }

    /**
     * Return the configured password or {@code null} if none was configured.
     * @return the configured password
     * @see #determinePassword()
     */
    public String getPassword() {
        return this.password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * Determine the password to use based on this configuration and the environment.
     * @return the password to use
     * @since 1.4.0
     */
    public String determinePassword() {
        if (StringUtils.hasText(this.password)) {
            return this.password;
        }
        if (EmbeddedDatabaseConnection.isEmbedded(determineDriverClassName())) {
            return "";
        }
        return null;
    }

    public String getJndiName() {
        return this.jndiName;
    }

    /**
     * Allows the DataSource to be managed by the container and obtained via JNDI. The
     * {@code URL}, {@code driverClassName}, {@code username} and {@code password} fields
     * will be ignored when using JNDI lookups.
     * @param jndiName the JNDI name
     */
    public void setJndiName(String jndiName) {
        this.jndiName = jndiName;
    }

    public boolean isInitialize() {
        return this.initialize;
    }

    public void setInitialize(boolean initialize) {
        this.initialize = initialize;
    }

    public String getPlatform() {
        return this.platform;
    }

    public void setPlatform(String platform) {
        this.platform = platform;
    }

    public List<String> getSchema() {
        return this.schema;
    }

    public void setSchema(List<String> schema) {
        this.schema = schema;
    }

    public String getSchemaUsername() {
        return this.schemaUsername;
    }

    public void setSchemaUsername(String schemaUsername) {
        this.schemaUsername = schemaUsername;
    }

    public String getSchemaPassword() {
        return this.schemaPassword;
    }

    public void setSchemaPassword(String schemaPassword) {
        this.schemaPassword = schemaPassword;
    }

    public List<String> getData() {
        return this.data;
    }

    public void setData(List<String> data) {
        this.data = data;
    }

    public String getDataUsername() {
        return this.dataUsername;
    }

    public void setDataUsername(String dataUsername) {
        this.dataUsername = dataUsername;
    }

    public String getDataPassword() {
        return this.dataPassword;
    }

    public void setDataPassword(String dataPassword) {
        this.dataPassword = dataPassword;
    }

    public boolean isContinueOnError() {
        return this.continueOnError;
    }

    public void setContinueOnError(boolean continueOnError) {
        this.continueOnError = continueOnError;
    }

    public String getSeparator() {
        return this.separator;
    }

    public void setSeparator(String separator) {
        this.separator = separator;
    }

    public Charset getSqlScriptEncoding() {
        return this.sqlScriptEncoding;
    }

    public void setSqlScriptEncoding(Charset sqlScriptEncoding) {
        this.sqlScriptEncoding = sqlScriptEncoding;
    }

    public ClassLoader getClassLoader() {
        return this.classLoader;
    }

    public Xa getXa() {
        return this.xa;
    }

    public void setXa(Xa xa) {
        this.xa = xa;
    }

    /**
     * XA Specific datasource settings.
     */
    public static class Xa {

        /**
         * XA datasource fully qualified name.
         */
        private String dataSourceClassName;

        /**
         * Properties to pass to the XA data source.
         */
        private Map<String, String> properties = new LinkedHashMap<String, String>();

        public String getDataSourceClassName() {
            return this.dataSourceClassName;
        }

        public void setDataSourceClassName(String dataSourceClassName) {
            this.dataSourceClassName = dataSourceClassName;
        }

        public Map<String, String> getProperties() {
            return this.properties;
        }

        public void setProperties(Map<String, String> properties) {
            this.properties = properties;
        }

    }

    static class DataSourceBeanCreationException extends BeanCreationException {

        DataSourceBeanCreationException(EmbeddedDatabaseConnection connection,
                Environment environment, String property) {
            super(getMessage(connection, environment, property));
        }

        private static String getMessage(EmbeddedDatabaseConnection connection,
                Environment environment, String property) {
            StringBuilder message = new StringBuilder();
            message.append("Cannot determine embedded database " + property
                    + " for database type " + connection + ". ");
            message.append("If you want an embedded database please put a supported "
                    + "one on the classpath. ");
            message.append("If you have database settings to be loaded from a "
                    + "particular profile you may need to active it");
            if (environment != null) {
                String[] profiles = environment.getActiveProfiles();
                if (ObjectUtils.isEmpty(profiles)) {
                    message.append(" (no profiles are currently active)");
                }
                else {
                    message.append(" (the profiles \""
                            + StringUtils.arrayToCommaDelimitedString(
                                    environment.getActiveProfiles())
                            + "\" are currently active)");

                }
            }
            message.append(".");
            return message.toString();
        }
    }
}

 

 2.对JPA的自动配置

  Spring Boot对JPA的自动配置放置在org.springframework.boot.autoconfigure.orm.jpa下,如图

从HibernateJpaAutoConfiguration可以看出,Spring boot默认JPA的实现者是Hibernate;HibernateJpaAutoConfiguration依赖于DataSourceAutoConfiguration。

  1 @Configuration
  2 @ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class,
  3         EnableTransactionManagement.class, EntityManager.class })
  4 @Conditional(HibernateEntityManagerCondition.class)
  5 @AutoConfigureAfter({ DataSourceAutoConfiguration.class })
  6 public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration {
  7 
  8     private static final Log logger = LogFactory
  9             .getLog(HibernateJpaAutoConfiguration.class);
 10 
 11     private static final String JTA_PLATFORM = "hibernate.transaction.jta.platform";
 12 
 13     /**
 14      * {@code NoJtaPlatform} implementations for various Hibernate versions.
 15      */
 16     private static final String[] NO_JTA_PLATFORM_CLASSES = {
 17             "org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform",
 18             "org.hibernate.service.jta.platform.internal.NoJtaPlatform" };
 19 
 20     /**
 21      * {@code WebSphereExtendedJtaPlatform} implementations for various Hibernate
 22      * versions.
 23      */
 24     private static final String[] WEBSPHERE_JTA_PLATFORM_CLASSES = {
 25             "org.hibernate.engine.transaction.jta.platform.internal.WebSphereExtendedJtaPlatform",
 26             "org.hibernate.service.jta.platform.internal.WebSphereExtendedJtaPlatform", };
 27 
 28     public HibernateJpaAutoConfiguration(DataSource dataSource,
 29             JpaProperties jpaProperties,
 30             ObjectProvider<JtaTransactionManager> jtaTransactionManager,
 31             ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
 32         super(dataSource, jpaProperties, jtaTransactionManager,
 33                 transactionManagerCustomizers);
 34     }
 35 
 36     @Override
 37     protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
 38         return new HibernateJpaVendorAdapter();
 39     }
 40 
 41     @Override
 42     protected Map<String, Object> getVendorProperties() {
 43         Map<String, Object> vendorProperties = new LinkedHashMap<String, Object>();
 44         vendorProperties.putAll(getProperties().getHibernateProperties(getDataSource()));
 45         return vendorProperties;
 46     }
 47 
 48     @Override
 49     protected void customizeVendorProperties(Map<String, Object> vendorProperties) {
 50         super.customizeVendorProperties(vendorProperties);
 51         if (!vendorProperties.containsKey(JTA_PLATFORM)) {
 52             configureJtaPlatform(vendorProperties);
 53         }
 54     }
 55 
 56     private void configureJtaPlatform(Map<String, Object> vendorProperties)
 57             throws LinkageError {
 58         JtaTransactionManager jtaTransactionManager = getJtaTransactionManager();
 59         if (jtaTransactionManager != null) {
 60             if (runningOnWebSphere()) {
 61                 // We can never use SpringJtaPlatform on WebSphere as
 62                 // WebSphereUowTransactionManager has a null TransactionManager
 63                 // which will cause Hibernate to NPE
 64                 configureWebSphereTransactionPlatform(vendorProperties);
 65             }
 66             else {
 67                 configureSpringJtaPlatform(vendorProperties, jtaTransactionManager);
 68             }
 69         }
 70         else {
 71             vendorProperties.put(JTA_PLATFORM, getNoJtaPlatformManager());
 72         }
 73     }
 74 
 75     private boolean runningOnWebSphere() {
 76         return ClassUtils.isPresent(
 77                 "com.ibm.websphere.jtaextensions." + "ExtendedJTATransaction",
 78                 getClass().getClassLoader());
 79     }
 80 
 81     private void configureWebSphereTransactionPlatform(
 82             Map<String, Object> vendorProperties) {
 83         vendorProperties.put(JTA_PLATFORM, getWebSphereJtaPlatformManager());
 84     }
 85 
 86     private Object getWebSphereJtaPlatformManager() {
 87         return getJtaPlatformManager(WEBSPHERE_JTA_PLATFORM_CLASSES);
 88     }
 89 
 90     private void configureSpringJtaPlatform(Map<String, Object> vendorProperties,
 91             JtaTransactionManager jtaTransactionManager) {
 92         try {
 93             vendorProperties.put(JTA_PLATFORM,
 94                     new SpringJtaPlatform(jtaTransactionManager));
 95         }
 96         catch (LinkageError ex) {
 97             // NoClassDefFoundError can happen if Hibernate 4.2 is used and some
 98             // containers (e.g. JBoss EAP 6) wraps it in the superclass LinkageError
 99             if (!isUsingJndi()) {
100                 throw new IllegalStateException("Unable to set Hibernate JTA "
101                         + "platform, are you using the correct "
102                         + "version of Hibernate?", ex);
103             }
104             // Assume that Hibernate will use JNDI
105             if (logger.isDebugEnabled()) {
106                 logger.debug("Unable to set Hibernate JTA platform : " + ex.getMessage());
107             }
108         }
109     }
110 
111     private boolean isUsingJndi() {
112         try {
113             return JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable();
114         }
115         catch (Error ex) {
116             return false;
117         }
118     }
119 
120     private Object getNoJtaPlatformManager() {
121         return getJtaPlatformManager(NO_JTA_PLATFORM_CLASSES);
122     }
123 
124     private Object getJtaPlatformManager(String[] candidates) {
125         for (String candidate : candidates) {
126             try {
127                 return Class.forName(candidate).newInstance();
128             }
129             catch (Exception ex) {
130                 // Continue searching
131             }
132         }
133         throw new IllegalStateException("Could not configure JTA platform");
134     }
135 
136     @Order(Ordered.HIGHEST_PRECEDENCE + 20)
137     static class HibernateEntityManagerCondition extends SpringBootCondition {
138 
139         private static String[] CLASS_NAMES = {
140                 "org.hibernate.ejb.HibernateEntityManager",
141                 "org.hibernate.jpa.HibernateEntityManager" };
142 
143         @Override
144         public ConditionOutcome getMatchOutcome(ConditionContext context,
145                 AnnotatedTypeMetadata metadata) {
146             ConditionMessage.Builder message = ConditionMessage
147                     .forCondition("HibernateEntityManager");
148             for (String className : CLASS_NAMES) {
149                 if (ClassUtils.isPresent(className, context.getClassLoader())) {
150                     return ConditionOutcome
151                             .match(message.found("class").items(Style.QUOTE, className));
152                 }
153             }
154             return ConditionOutcome.noMatch(message.didNotFind("class", "classes")
155                     .items(Style.QUOTE, Arrays.asList(CLASS_NAMES)));
156         }
157 
158     }
159 
160 }

  从JpaProperties的源码可以看出,配置JPA可以使用spring.jpa为前缀的属性在application.properties中配置。

  从JpaBaseConfiguration的源码中可以看出,Spring boot为我们配置了transactionManager、jpaVendorAdapter、entityManagerFactory等Bean。JpaBaseConfiguration还有一个getPackagesToScan方法,可以自动扫描注解有Entity的实体类。

  在web项目中我们经常会遇到在控制器或者页面访问数据的时候出现会话连接已关闭的错误,这个时候我们会配置一个Open EntityManager(Session)In View这个过滤器。令人惊喜的是,Spring boot为我们自动配置了OpenEntityManagerInViewIntercept这个bean,并注册到Spring mvc的拦截器中。

  3. 对Spring Data JPA的自动配置

而Spring boot对Spring Data Jpa的自动配置放置在org.springframework.boot.autoconfigure.data.jpa下,如图

从JpaRepositoriesAutoConfiguration和JPARepositoriesAutoConfigureRegistrar源码可以看出,JpaRepositoriesAutoConfiguration是依赖于HibernateJpaAutoConfiguration配置的,且Spring boot自动开启了对spring data jpa的支持,即我们无须在配置类中显示声明@EnableJpaRepositories.

 

总结 : 通过上面的分析,在Spring boot下使用Spring Data JPA,在项目的maven依赖里添加spring-boot-starter-data-jpa,然后只需定义DataSource、实体类和数据访问层,并在需要使用数据访问的地方注入数据访问层的Bean即可,无须任何额外配置。

 

posted @ 2017-04-22 12:30  kevin_shen  阅读(9278)  评论(0编辑  收藏  举报