Elr*_*ond 5 spring tomcat hibernate jpa entitymanager
我有一个使用Hibernate/JPA,Spring和Jersey的应用程序.在我的应用程序上下文中,我设置数据源,定义实体管理器工厂,使用该实体管理器工厂设置事务管理器,并使用事务注释注释各种服务方法,因此我还有tx:annotation-driven definition to wire在我的交易经理需要的地方.这个设置很好,我已经能够读写得很好.我想转到数据库设置,我有一个主机与多个从属(MySQL).因此,我希望所有使用transactional注释的方法都使用指向主数据库服务器的数据源,并使用所有其他方法来使用从服务器的连接池.
我尝试创建两个不同的数据源,两个不同的实体管理器工厂和两个不同的持久性单元 - 至少可以说是丑陋的.我尝试了一个MySQL代理,但是我们遇到了更多问题.连接池已在servlet容器中处理.我可以在Tomcat中实现读取事务并将其定向到正确的数据库服务器的东西,还是有办法让所有那些使用事务注释注释的方法使用特定的数据源?
Vla*_*cea 12
要将读写事务路由到主节点,将只读事务路由到副本节点,我们可以定义一个ReadWriteDataSource连接到主节点的节点和一个ReadOnlyDataSource连接到副本节点的节点。
读写和只读事务路由是由 SpringAbstractRoutingDataSource抽象完成的,它是由 实现的TransactionRoutingDatasource,如下图所示:
它TransactionRoutingDataSource非常容易实现,如下所示:
public class TransactionRoutingDataSource
extends AbstractRoutingDataSource {
@Nullable
@Override
protected Object determineCurrentLookupKey() {
return TransactionSynchronizationManager
.isCurrentTransactionReadOnly() ?
DataSourceType.READ_ONLY :
DataSourceType.READ_WRITE;
}
}
Run Code Online (Sandbox Code Playgroud)
基本上,我们检查存储当前事务上下文的 SpringTransactionSynchronizationManager类,以检查当前运行的 Spring 事务是否是只读的。
该determineCurrentLookupKey方法返回将用于选择读写或只读 JDBC 的鉴别器值DataSource。
这DataSourceType只是一个基本的 Java 枚举,它定义了我们的事务路由选项:
public enum DataSourceType {
READ_WRITE,
READ_ONLY
}
Run Code Online (Sandbox Code Playgroud)
配置DataSource如下:
@Configuration
@ComponentScan(
basePackages = "com.vladmihalcea.book.hpjp.util.spring.routing"
)
@PropertySource(
"/META-INF/jdbc-postgresql-replication.properties"
)
public class TransactionRoutingConfiguration
extends AbstractJPAConfiguration {
@Value("${jdbc.url.primary}")
private String primaryUrl;
@Value("${jdbc.url.replica}")
private String replicaUrl;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource readWriteDataSource() {
PGSimpleDataSource dataSource = new PGSimpleDataSource();
dataSource.setURL(primaryUrl);
dataSource.setUser(username);
dataSource.setPassword(password);
return connectionPoolDataSource(dataSource);
}
@Bean
public DataSource readOnlyDataSource() {
PGSimpleDataSource dataSource = new PGSimpleDataSource();
dataSource.setURL(replicaUrl);
dataSource.setUser(username);
dataSource.setPassword(password);
return connectionPoolDataSource(dataSource);
}
@Bean
public TransactionRoutingDataSource actualDataSource() {
TransactionRoutingDataSource routingDataSource =
new TransactionRoutingDataSource();
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put(
DataSourceType.READ_WRITE,
readWriteDataSource()
);
dataSourceMap.put(
DataSourceType.READ_ONLY,
readOnlyDataSource()
);
routingDataSource.setTargetDataSources(dataSourceMap);
return routingDataSource;
}
@Override
protected Properties additionalProperties() {
Properties properties = super.additionalProperties();
properties.setProperty(
"hibernate.connection.provider_disables_autocommit",
Boolean.TRUE.toString()
);
return properties;
}
@Override
protected String[] packagesToScan() {
return new String[]{
"com.vladmihalcea.book.hpjp.hibernate.transaction.forum"
};
}
@Override
protected String databaseType() {
return Database.POSTGRESQL.name().toLowerCase();
}
protected HikariConfig hikariConfig(
DataSource dataSource) {
HikariConfig hikariConfig = new HikariConfig();
int cpuCores = Runtime.getRuntime().availableProcessors();
hikariConfig.setMaximumPoolSize(cpuCores * 4);
hikariConfig.setDataSource(dataSource);
hikariConfig.setAutoCommit(false);
return hikariConfig;
}
protected HikariDataSource connectionPoolDataSource(
DataSource dataSource) {
return new HikariDataSource(hikariConfig(dataSource));
}
}
Run Code Online (Sandbox Code Playgroud)
资源/META-INF/jdbc-postgresql-replication.properties文件提供了读写和只读 JDBCDataSource组件的配置:
hibernate.dialect=org.hibernate.dialect.PostgreSQL10Dialect
jdbc.url.primary=jdbc:postgresql://localhost:5432/high_performance_java_persistence
jdbc.url.replica=jdbc:postgresql://localhost:5432/high_performance_java_persistence_replica
jdbc.username=postgres
jdbc.password=admin
Run Code Online (Sandbox Code Playgroud)
该jdbc.url.primary属性定义主节点的 URL,而jdbc.url.replica定义副本节点的 URL。
SpringreadWriteDataSource组件定义了读写JDBC DataSource,而readOnlyDataSource组件定义了只读JDBC DataSource。
请注意,读写和只读数据源都使用 HikariCP 进行连接池。有关使用数据库连接池的好处的更多详细信息。
它actualDataSource充当读写和只读数据源的外观,并使用该TransactionRoutingDataSource实用程序来实现。
readWriteDataSource使用密钥进行注册并DataSourceType.READ_WRITE使用readOnlyDataSource密钥进行注册DataSourceType.READ_ONLY。
因此,当执行读写@Transactional方法时,readWriteDataSource将使用 ,而当执行@Transactional(readOnly = true)方法时,readOnlyDataSource将使用 。
请注意,该
additionalProperties方法定义了hibernate.connection.provider_disables_autocommitHibernate 属性,我将其添加到 Hibernate 以推迟 RESOURCE_LOCAL JPA 事务的数据库获取。不仅
hibernate.connection.provider_disables_autocommit可以让您更好地利用数据库连接,而且这是我们使此示例正常工作的唯一方法,因为如果没有此配置,将在调用该determineCurrentLookupKey方法之前获取连接TransactionRoutingDataSource。
构建 JPA 所需的其余 Spring 组件EntityManagerFactory由基类定义AbstractJPAConfiguration。
基本上,actualDataSource由 DataSource-Proxy 进一步包装并提供给 JPA ENtityManagerFactory。您可以在 GitHub 上查看源代码以了解更多详细信息。
为了检查事务路由是否有效,我们将通过在postgresql.conf配置文件中设置以下属性来启用 PostgreSQL 查询日志:
log_min_duration_statement = 0
log_line_prefix = '[%d] '
Run Code Online (Sandbox Code Playgroud)
该log_min_duration_statement属性设置用于记录所有 PostgreSQL 语句,而第二个属性设置将数据库名称添加到 SQL 日志中。
因此,在调用newPost和findAllPostsByTitle方法时,如下所示:
Post post = forumService.newPost(
"High-Performance Java Persistence",
"JDBC", "JPA", "Hibernate"
);
List<Post> posts = forumService.findAllPostsByTitle(
"High-Performance Java Persistence"
);
Run Code Online (Sandbox Code Playgroud)
我们可以看到 PostgreSQL 记录了以下消息:
[high_performance_java_persistence] LOG: execute <unnamed>:
BEGIN
[high_performance_java_persistence] DETAIL:
parameters: $1 = 'JDBC', $2 = 'JPA', $3 = 'Hibernate'
[high_performance_java_persistence] LOG: execute <unnamed>:
select tag0_.id as id1_4_, tag0_.name as name2_4_
from tag tag0_ where tag0_.name in ($1 , $2 , $3)
[high_performance_java_persistence] LOG: execute <unnamed>:
select nextval ('hibernate_sequence')
[high_performance_java_persistence] DETAIL:
parameters: $1 = 'High-Performance Java Persistence', $2 = '4'
[high_performance_java_persistence] LOG: execute <unnamed>:
insert into post (title, id) values ($1, $2)
[high_performance_java_persistence] DETAIL:
parameters: $1 = '4', $2 = '1'
[high_performance_java_persistence] LOG: execute <unnamed>:
insert into post_tag (post_id, tag_id) values ($1, $2)
[high_performance_java_persistence] DETAIL:
parameters: $1 = '4', $2 = '2'
[high_performance_java_persistence] LOG: execute <unnamed>:
insert into post_tag (post_id, tag_id) values ($1, $2)
[high_performance_java_persistence] DETAIL:
parameters: $1 = '4', $2 = '3'
[high_performance_java_persistence] LOG: execute <unnamed>:
insert into post_tag (post_id, tag_id) values ($1, $2)
[high_performance_java_persistence] LOG: execute S_3:
COMMIT
[high_performance_java_persistence_replica] LOG: execute <unnamed>:
BEGIN
[high_performance_java_persistence_replica] DETAIL:
parameters: $1 = 'High-Performance Java Persistence'
[high_performance_java_persistence_replica] LOG: execute <unnamed>:
select post0_.id as id1_0_, post0_.title as title2_0_
from post post0_ where post0_.title=$1
[high_performance_java_persistence_replica] LOG: execute S_1:
COMMIT
Run Code Online (Sandbox Code Playgroud)
使用前缀的日志语句high_performance_java_persistence在主节点上执行,而使用 的日志high_performance_java_persistence_replica语句在副本节点上执行。
所以,一切都像魅力一样!
所有源代码都可以在我的High-Performance Java Persistence GitHub 存储库中找到,因此您也可以尝试一下。
这个要求非常有用,因为单主数据库复制架构不仅提供容错和更好的可用性,而且允许我们通过添加更多副本节点来扩展读取操作。
这就是我最终做的事情并且效果很好.实体管理器只能有一个bean用作数据源.所以我要做的就是创建一个在必要时在两者之间路由的bean.那个ben是我用于JPA实体经理的那个.
我在tomcat中设置了两个不同的数据源.在server.xml中,我创建了两个资源(数据源).
<Resource name="readConnection" auth="Container" type="javax.sql.DataSource"
username="readuser" password="readpass"
url="jdbc:mysql://readipaddress:3306/readdbname"
driverClassName="com.mysql.jdbc.Driver"
initialSize="5" maxWait="5000"
maxActive="120" maxIdle="5"
validationQuery="select 1"
poolPreparedStatements="true"
removeAbandoned="true" />
<Resource name="writeConnection" auth="Container" type="javax.sql.DataSource"
username="writeuser" password="writepass"
url="jdbc:mysql://writeipaddress:3306/writedbname"
driverClassName="com.mysql.jdbc.Driver"
initialSize="5" maxWait="5000"
maxActive="120" maxIdle="5"
validationQuery="select 1"
poolPreparedStatements="true"
removeAbandoned="true" />
Run Code Online (Sandbox Code Playgroud)
你可以在同一台服务器上安装数据库表,在这种情况下,ip地址或域名将是相同的,只是不同的dbs - 你得到了jist.
然后我在tomcat的context.xml文件中添加了一个资源链接,它引用了这些资源.
<ResourceLink name="readConnection" global="readConnection" type="javax.sql.DataSource"/>
<ResourceLink name="writeConnection" global="writeConnection" type="javax.sql.DataSource"/>
Run Code Online (Sandbox Code Playgroud)
这些资源链接是spring在应用程序上下文中读取的内容.
在应用程序上下文中,我为每个资源链接添加了一个bean定义,并添加了一个额外的bean定义,该定义引用了我创建的数据源路由器bean,它接受了之前创建的两个bean(bean定义)的映射(枚举).
<!--
Data sources representing master (write) and slaves (read).
-->
<bean id="readDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="readConnection" />
<property name="resourceRef" value="true" />
<property name="lookupOnStartup" value="true" />
<property name="cache" value="true" />
<property name="proxyInterface" value="javax.sql.DataSource" />
</bean>
<bean id="writeDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="writeConnection" />
<property name="resourceRef" value="true" />
<property name="lookupOnStartup" value="true" />
<property name="cache" value="true" />
<property name="proxyInterface" value="javax.sql.DataSource" />
</bean>
<!--
Provider of available (master and slave) data sources.
-->
<bean id="dataSource" class="com.myapp.dao.DatasourceRouter">
<property name="targetDataSources">
<map key-type="com.myapp.api.util.AvailableDataSources">
<entry key="READ" value-ref="readDataSource"/>
<entry key="WRITE" value-ref="writeDataSource"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="writeDataSource"/>
</bean>
Run Code Online (Sandbox Code Playgroud)
然后,实体管理器bean定义引用了dataSource bean.
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="persistenceUnitName" value="${jpa.persistenceUnitName}" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="databasePlatform" value="${jpa.dialect}"/>
<property name="showSql" value="${jpa.showSQL}" />
</bean>
</property>
</bean>
Run Code Online (Sandbox Code Playgroud)
我在属性文件中定义了一些属性,但您可以使用您自己的特定值替换$ {}值.所以现在我有一个bean使用另外两个代表我的两个数据源的bean.一个bean是我用于JPA的bean.它没有发现任何路由发生.
所以现在是路由bean.
public class DatasourceRouter extends AbstractRoutingDataSource{
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException{
// TODO Auto-generated method stub
return null;
}
@Override
protected Object determineCurrentLookupKey(){
return DatasourceProvider.getDatasource();
}
}
Run Code Online (Sandbox Code Playgroud)
实体管理器调用重写的方法来基本确定数据源.DatasourceProvider具有线程本地(线程安全)属性,具有getter和setter方法以及用于清理的clear数据源方法.
public class DatasourceProvider{
private static final ThreadLocal<AvailableDataSources> datasourceHolder = new ThreadLocal<AvailableDataSources>();
public static void setDatasource(final AvailableDataSources customerType){
datasourceHolder.set(customerType);
}
public static AvailableDataSources getDatasource(){
return (AvailableDataSources) datasourceHolder.get();
}
public static void clearDatasource(){
datasourceHolder.remove();
}
}
Run Code Online (Sandbox Code Playgroud)
我有一个通用的DAO实现,我使用的方法来处理各种例程JPA调用(getReference,persist,createNamedQUery和getResultList等).在调用entityManager做任何需要做的事情之前,我将DatasourceProvider的数据源设置为read或write.该方法可以处理传入的值,使其更具动态性.这是一个示例方法.
@Override
public List<T> findByNamedQuery(final String queryName, final Map<String, Object> properties, final int... rowStartIdxAndCount)
{
DatasourceProvider.setDatasource(AvailableDataSources.READ);
final TypedQuery<T> query = entityManager.createNamedQuery(queryName, persistentClass);
if (!properties.isEmpty())
{
bindNamedQueryParameters(query, properties);
}
appyRowLimits(query, rowStartIdxAndCount);
return query.getResultList();
}
Run Code Online (Sandbox Code Playgroud)
AvailableDataSources是一个带有READ或WRITE的枚举,它引用相应的数据源.您可以在我的bean中在应用程序上下文中定义的映射中看到它.
| 归档时间: |
|
| 查看次数: |
7733 次 |
| 最近记录: |