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即可,無須任何額外配置。