#69 - Added example for how to set up Spring Data JPA with two DataSources.

This commit is contained in:
Oliver Gierke
2015-03-19 10:04:46 +01:00
parent 65cef4132d
commit 9ecb720f4a
13 changed files with 640 additions and 0 deletions

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2015 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 example.springdata.jpa.multipleds;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import example.springdata.jpa.multipleds.customer.Customer.CustomerId;
/**
* Core Spring Boot application configuration. Note, that we explicitly deactivate some auto-configurations explicitly.
* They mostly will even disable automatically if special bean names are used (e.g. {@code entityManagerFactory}) but I
* wanted to keep the two configurations symmetric. The configuration classes being located in separate packages serves
* the purpose of scoping the Spring Data repository scanning to those packages so that the infrastructure setup is
* attached to the corresponding repository instances.
*
* @author Oliver Gierke
* @see example.springdata.jpa.multipleds.customer.CustomerConfig
* @see example.springdata.jpa.multipleds.order.OrderConfig
*/
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class })
@EnableTransactionManagement
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Autowired DataInitializer initializer;
@PostConstruct
public void init() {
CustomerId customerId = initializer.initializeCustomer();
initializer.initializeOrder(customerId);
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2015 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 example.springdata.jpa.multipleds;
import javax.sql.DataSource;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import example.springdata.jpa.multipleds.customer.Customer;
import example.springdata.jpa.multipleds.customer.Customer.CustomerId;
import example.springdata.jpa.multipleds.customer.CustomerRepository;
import example.springdata.jpa.multipleds.order.Order;
import example.springdata.jpa.multipleds.order.Order.LineItem;
import example.springdata.jpa.multipleds.order.OrderRepository;
/**
* Sample component to demonstrate how to work with repositories backed by different {@link DataSource}s. Note how we
* explicitly select a transaction manager by name. In this particular case (only one operation on the repository) this
* is not strictly necessary. However, if multiple repositories or multiple interactions on the very same repsoitory are
* to be executed in a method we need to expand the transaction boundary around these interactions. It's recommended to
* create a dedicated annotation meta-annotated with {@code @Transactional("…")} to be able to refer to a particular
* datasource without using String qualifiers.
* <p>
* Also, not that one cannot interact with both databases in a single, transactional method as transactions are thread
* bound in Spring an thus only a single transaction can be active in a single thread. See {@link Application#init()}
* for how to orchestrate the calls.
*
* @author Oliver Gierke
*/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class DataInitializer {
private final @NonNull OrderRepository orders;
private final @NonNull CustomerRepository customers;
/**
* Initializes a {@link Customer}.
*
* @return
*/
@Transactional("customerTransactionManager")
public CustomerId initializeCustomer() {
return customers.save(new Customer("Dave", "Matthews")).getId();
}
/**
* Initializes an {@link Order}.
*
* @param customer must not be {@literal null}.
* @return
*/
@Transactional("orderTransactionManager")
public Order initializeOrder(CustomerId customer) {
Assert.notNull(customer, "Custoemr identifier must not be null!");
Order order = new Order(customer);
order.add(new LineItem("Lakewood Guitar"));
return orders.save(order);
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2015 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 example.springdata.jpa.multipleds.customer;
import java.io.Serializable;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import lombok.Value;
/**
* Simple domain class representing a {@link Customer}.
*
* @author Oliver Gierke
*/
@Entity
@EqualsAndHashCode(of = "id")
@Getter
@RequiredArgsConstructor
@ToString
public class Customer {
private @Id @GeneratedValue Long id;
private final String firstname, lastname;
Customer() {
this.firstname = null;
this.lastname = null;
}
public CustomerId getId() {
return new CustomerId(id);
}
@Value
@Embeddable
@RequiredArgsConstructor
@SuppressWarnings("serial")
public static class CustomerId implements Serializable {
private final Long customerId;
CustomerId() {
this.customerId = null;
}
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2015 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 example.springdata.jpa.multipleds.customer;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
/**
* Configuration for the {@link Customer} slice of the system. A dedicated {@link DataSource},
* {@link JpaTransactionManager} and {@link EntityManagerFactory}. Note that there could of course be some deduplication
* with {@link example.springdata.jpa.multipleds.order.OrderConfig}. I just decided to keep it to focus on the
* sepeartion of the two. Also, some overlaps might not even occur in real world scenarios (whether to create DDl or the
* like).
*
* @author Oliver Gierke
*/
@Configuration
@EnableJpaRepositories(entityManagerFactoryRef = "customerEntityManagerFactory",
transactionManagerRef = "customerTransactionManager")
class CustomerConfig {
@Bean
PlatformTransactionManager customerTransactionManager() {
return new JpaTransactionManager(customerEntityManagerFactory().getObject());
}
@Bean
LocalContainerEntityManagerFactoryBean customerEntityManagerFactory() {
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
jpaVendorAdapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
factoryBean.setDataSource(customerDataSource());
factoryBean.setJpaVendorAdapter(jpaVendorAdapter);
factoryBean.setPackagesToScan(CustomerConfig.class.getPackage().getName());
return factoryBean;
}
@Bean
DataSource customerDataSource() {
return new EmbeddedDatabaseBuilder().//
setType(EmbeddedDatabaseType.HSQL).//
setName("customers").//
build();
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2015 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 example.springdata.jpa.multipleds.customer;
import java.util.Optional;
import org.springframework.data.repository.CrudRepository;
/**
* Spring Data repository to manage {@link Customer}s.
*
* @author Oliver Gierke
*/
public interface CustomerRepository extends CrudRepository<Customer, Long> {
Optional<Customer> findByLastname(String lastname);
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2015 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 example.springdata.jpa.multipleds.order;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import org.springframework.util.Assert;
import example.springdata.jpa.multipleds.customer.Customer.CustomerId;
/**
* Simple domain class representing an {@link Order}
*
* @author Oliver Gierke
*/
@Entity
@EqualsAndHashCode(of = "id")
@Getter
@RequiredArgsConstructor
@ToString
// Needs to be explicitly named as Order is a reserved keyword
@Table(name = "SampleOrder")
public class Order {
private @Id @GeneratedValue Long id;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)//
private final List<LineItem> lineItems = new ArrayList<LineItem>();
private final CustomerId customer;
Order() {
this.customer = null;
}
/**
* Adds a {@link LineItem} to the {@link Order}.
*
* @param lineItem must not be {@literal null}.
*/
public void add(LineItem lineItem) {
Assert.notNull(lineItem, "Line item must not be null!");
this.lineItems.add(lineItem);
}
@Entity
@EqualsAndHashCode(of = "id")
@Getter
@RequiredArgsConstructor
@ToString
@Table(name = "LineItem")
public static class LineItem {
private @Id @GeneratedValue Long id;
private final String description;
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2015 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 example.springdata.jpa.multipleds.order;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
/**
* Configuration for the {@link Order} slice of the system. A dedicated {@link DataSource},
* {@link JpaTransactionManager} and {@link EntityManagerFactory}. Note that there could of course be some deduplication
* with {@link example.springdata.jpa.multipleds.customer.CustomerConfig}. I just decided to keep it to focus on the
* sepeartion of the two. Also, some overlaps might not even occur in real world scenarios (whether to create DDl or the
* like).
*
* @author Oliver Gierke
*/
@Configuration
@EnableJpaRepositories(entityManagerFactoryRef = "orderEntityManagerFactory",
transactionManagerRef = "orderTransactionManager")
class OrderConfig {
@Bean
PlatformTransactionManager orderTransactionManager() {
return new JpaTransactionManager(orderEntityManagerFactory().getObject());
}
@Bean
LocalContainerEntityManagerFactoryBean orderEntityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
factoryBean.setDataSource(orderDataSource());
factoryBean.setJpaVendorAdapter(vendorAdapter);
factoryBean.setPackagesToScan(OrderConfig.class.getPackage().getName());
return factoryBean;
}
@Bean
DataSource orderDataSource() {
return new EmbeddedDatabaseBuilder().//
setType(EmbeddedDatabaseType.HSQL).//
setName("orders").//
build();
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2015 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 example.springdata.jpa.multipleds.order;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
import example.springdata.jpa.multipleds.customer.Customer;
import example.springdata.jpa.multipleds.customer.Customer.CustomerId;
/**
* Spring Data repository managing {@link Order}s.
*
* @author Oliver Gierke
*/
public interface OrderRepository extends CrudRepository<Order, Long> {
/**
* Returns all {@link Order}s for the {@link Customer} with the given identifier.
*
* @param id must not be {@literal null}.
* @return
*/
List<Order> findByCustomer(CustomerId id);
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2015 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 example.springdata.jpa.multipleds.customer;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.util.Optional;
import javax.persistence.EntityManager;
import javax.transaction.Transactional;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import example.springdata.jpa.multipleds.Application;
/**
* Integration test for {@link CustomerRepository}.
*
* @author Oliver Gierke
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@Transactional
@TransactionConfiguration(transactionManager = "customerTransactionManager")
public class CustomerRepositoryTests {
@Autowired CustomerRepository repository;
@Autowired @Qualifier("customerEntityManagerFactory") EntityManager em;
@Test
public void findsCustomerByLastname() {
Optional<Customer> result = repository.findByLastname("Matthews");
assertThat(result, is(not(Optional.empty())));
assertThat(result.get().getFirstname(), is("Dave"));
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2015 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 example.springdata.jpa.multipleds.order;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import javax.transaction.Transactional;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import example.springdata.jpa.multipleds.Application;
import example.springdata.jpa.multipleds.customer.CustomerRepository;
/**
* Integration test for {@link CustomerRepository}.
*
* @author Oliver Gierke
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@Transactional
@TransactionConfiguration(transactionManager = "orderTransactionManager")
public class OrderRepositoryTests {
@Autowired OrderRepository orders;
@Autowired CustomerRepository customers;
@Test
public void persistsAndFindsCustomer() {
customers.findAll().forEach(customer -> {
assertThat(orders.findByCustomer(customer.getId()), hasSize(1));
});
}
}