spring data
DESCRIPTION
spring data repositoryTRANSCRIPT
M.C. Kang
Overview Spring Data is a high level SpringSource
project whose purpose is to unify and ease
the access to different kinds of persistence
stores, both relational database systems
and NoSQL data stores.
Source http://www.infoq.com/articles/spring-data-intro
Spring Data projects support the followings aspects:
Templating
Object/Datastore mapping
Repository support
Overview - Templates
The main purpose of a Spring Data template (and all other Spring templates) is resource
allocation and exception translation.
A template offers store specific operations like saving, updating and deleting a single record or
for executing queries or map/reduce jobs. But all these methods work only for the corresponding
underlying datastore.
MongoDb Template Configuration Example
<!-- Connection to MongoDB server -->
<mongo:db-factory host="localhost" port="27017" dbname="test" />
<!-- MongoDB Template -->
<bean id="mongoTemplate“
class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/>
</bean>
Overview - Object/Datastore Mapping
With Spring Data, this support is extended to NoSQL datastores with object-like data
structures. But these data structures can be quite different from each other, so it would be
difficult to make up a common API for object/datastore mapping. Each type of datastore
comes with its own set of annotations to provide the needed meta information for the
mapping.
@Entity
@Table(name="TUSR")
public class User {
@Id private String id;
@Column(name="fn") private String name;
private Date lastLogin;
...
}
@Document( collection="usr")
public class User {
@Id private String id;
@Field("fn") private String name;
private Date lastLogin;
...
}
@NodeEntity
public class User {
@GraphId Long id;
private String name;
private Date lastLogin;
...
}
JPA MongoDB Neo4j
Overview - Repository Support
generic support for CRUD and paging , sorting by providing special parameters to the finder
methods for all persistence stores
The main advantages of repository support are:
The developer writes a lot less boilerplate code
Queries can by defined alongside the finder method and its documentation
As a bonus, the JPQL queries are compiled as soon as the Spring context is assembled, not
the first time you use the query, which makes it easier to detect syntax errors
Data Model
Customer Address
CREATE TABLE customer ( id BIGINT IDENTITY PRIMARY KEY, firstname VARCHAR(255), lastname VARCHAR(255), email_address VARCHAR(255));
CREATE UNIQUE INDEX ix_customer_email ON CUSTOMER (email_address ASC);
CREATE TABLE address ( id BIGINT IDENTITY PRIMARY KEY, customer_id BIGINT CONSTRAINT address_customer_ref REFERENCES customer (id), street VARCHAR(255), city VARCHAR(255), country VARCHAR(255));
Domain Model
<<Entity>>
Customer
<<Entity>>
Address
<<Embeddable>
>
EmailAddress
<<MappedSuperClass>>
AbstractEntity
Step1. “Define Domain Model”
AbstractEntity@MappedSuperclasspublic class AbstractEntity {
@Id@GeneratedValue(strategy = GenerationType.AUTO)private Long id;
public Long getId() {return id;
}
public boolean equals(Object obj) {if (this == obj) {return true;}
if (this.id == null || obj == null || !(this.getClass().equals(obj.getClass()))) {
return false;}
AbstractEntity that = (AbstractEntity) obj;
return this.id.equals(that.getId());}
public int hashCode() {return id == null ? 0 : id.hashCode();
}}
Customer@Entitypublic class Customer extends AbstractEntity {
private String firstname, lastname;
@Column(unique = true)private EmailAddress emailAddress;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)@JoinColumn(name = "customer_id")private Set<Address> addresses = new HashSet<Address>();
public Customer(String firstname, String lastname) {
Assert.hasText(firstname);Assert.hasText(lastname);
this.firstname = firstname;this.lastname = lastname;
}
protected Customer() {
}
public void add(Address address) {
Assert.notNull(address);this.addresses.add(address);
}…
Step1. “Define Domain Model”
@MappedSuperclass to express that it is not a managedentity class on its own but rather will be extended by entity classes.
If there were demand to customize the names of the columns to which the properties would be persisted, you could use the @Column annotation.
Address@Entitypublic class Address extends AbstractEntity {
private String street, city, country;
public Address(String street, String city, String country) {
Assert.hasText(street, "Street must not be null or empty!");Assert.hasText(city, "City must not be null or empty!");Assert.hasText(country, "Country must not be null or empty!");
this.street = street;this.city = city;this.country = country;
}
protected Address() {
} …
EmailAddress@Embeddablepublic class EmailAddress {
@Column(name = "email_address")private String value;
public EmailAddress(String emailAddress) {Assert.isTrue(isValid(emailAddress), "Invalid email address!");this.value = emailAddress;
}
protected EmailAddress() {
}…
public interface CustomerRepository extends CrudRepository<Customer, Long> {
Customer findByEmailAddress(EmailAddress emailAddress);Customer findById(Long id);List<Customer> findAll();
}
CustomerRepository
Step1. “Define Domain Model”
Step2. “Define Repository Interface”
-Just only interface
the EmailAddress class is an @Embeddable, which will cause the persistence provider to flatten out all properties of it into the table of the surrounding class.
@Configuration@EnableTransactionManagement@ComponentScan@EnableJpaRepositoriespublic class InfrastructureConfig {
@Beanpublic DataSource dataSource() {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL).addScript("classpath:sql/schema.sql").addScript("classpath:sql/test-data.sql").build();
}
@Beanpublic LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();vendorAdapter.setDatabase(Database.HSQL);//vendorAdapter.setGenerateDdl(true);vendorAdapter.setShowSql(true);LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();factory.setJpaVendorAdapter(vendorAdapter);factory.setPackagesToScan(getClass().getPackage().getName());factory.setDataSource(dataSource());return factory;
}
@Beanpublic PlatformTransactionManager transactionManager() {
JpaTransactionManager txManager = new JpaTransactionManager();txManager.setEntityManagerFactory(entityManagerFactory().getObject());return txManager;
}}
SpringConfig
Step3. “Configure Spring Beans”
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = { TestConfig.class })@Transactional@DirtiesContextpublic class TestMain {
@AutowiredCustomerRepository repository;
@AutowiredDataSource dataSource;
@Testpublic void testFindAll() {List<Customer> results = repository.findAll();assertThat(results, is(notNullValue()));assertThat(results, hasSize(3));assertThat(results.get(0), notNullValue());assertThat(results.get(1), notNullValue());assertThat(results.get(2), notNullValue());}
@Testpublic void testFindById() {Customer result = repository.findOne(100L);assertThat(result, is(notNullValue()));assertThat(result.getFirstname(), is("John"));}
@Testpublic void testFindByEmail() {Customer result = repository.findByEmailAddress(new EmailAddress("[email protected]"));assertThat(result, is(notNullValue()));assertThat(result.getFirstname(), is("Bob"));}
TestCase@Testpublic void saveNewCustomer() {Customer c = new Customer();//c.setFirstname("Sven");c.setLastname("Svensson");c.setEmailAddress(new EmailAddress("[email protected]"));Address a = new Address("Storgaten 6", "Trosa", "Sweden");c.add(a);repository.save(c);System.out.println(repository.findAll());Customer result = repository.findOne(c.getId());assertThat(result, is(notNullValue()));//assertThat(result.getFirstName(), is("Sven"));assertThat(result.getEmailAddress().toString(), is(notNullValue()));}
@Test(expected = DataIntegrityViolationException.class)public void saveNewCustomerWithDuplicateEmail() {Customer c = new Customer("Bob","Doe");c.setEmailAddress(new EmailAddress("[email protected]"));Address a = new Address("66 Main St", "Middletown", "USA");c.add(a);repository.save(c);}
@Testpublic void deleteCustomer() {Customer c = repository.findOne(100L);repository.delete(c);Customer result = repository.findOne(100L);assertThat(result, is(nullValue()));}
}
Step4. “Test it…”
Document Model
Customer
(*)Address
{ firstname : "Dave", lastname : "Matthews", email : { email : "[email protected]" }, addresses : [ { street : "Broadway", city : "New York", country : "United States" } ]}
Domain Model
<<Document>
>
Customer
<<Entity>>
Address
<<Embeddable>
>
EmailAddress
AbstractDocument
Step1. “Define Domain Model”
AbstractDocumentpublic class AbstractDocument {
@Idprivate BigInteger id;
public BigInteger getId() {return id;
}
public boolean equals(Object obj) {
if (this == obj) {return true;}
if (this.id == null || obj == null || !(this.getClass().equals(obj.getClass()))) {return false;}
AbstractDocument that = (AbstractDocument) obj;
return this.id.equals(that.getId());}
public int hashCode() {return id == null ? 0 : id.hashCode();
}}
Customer@Documentpublic class Customer extends AbstractDocument {
private String firstname, lastname;
@Field("email")@Indexed(unique = true)private EmailAddress emailAddress;private Set<Address> addresses = new HashSet<Address>();
public Customer(String firstname, String lastname) {
Assert.hasText(firstname);Assert.hasText(lastname);
this.firstname = firstname;this.lastname = lastname;}
protected Customer() {
}…
Step1. “Define Domain Model”
Addresspublic class Address {
private final String street, city, country;
public Address(String street, String city, String country) {
Assert.hasText(street, "Street must not be null or empty!");Assert.hasText(city, "City must not be null or empty!");Assert.hasText(country, "Country must not be null or empty!");
this.street = street;this.city = city;this.country = country;
} …
EmailAddresspublic class EmailAddress {
@Field("email")private final String value;
public EmailAddress(String emailAddress) {Assert.isTrue(isValid(emailAddress), "Invalid email address!");this.value = emailAddress;
}
protected EmailAddress() {
}…
public interface CustomerRepository extends CrudRepository<Customer, Long> {
Customer findByEmailAddress(EmailAddress emailAddress);Customer findById(Long id);List<Customer> findAll();
}
CustomerRepository
Step1. “Define Domain Model”
Step2. “Define Repository Interface”
-Just only interface
@Configuration@ComponentScan@EnableMongoRepositories(basePackages="com.mckang.springdata.mongo")class ApplicationConfig extends AbstractMongoConfiguration {
@Autowiredprivate List<Converter<?, ?>> converters;
protected String getDatabaseName() {return "e-store";
}
public Mongo mongo() throws Exception {
Mongo mongo = new Mongo("127.0.0.1");mongo.setWriteConcern(WriteConcern.FSYNC_SAFE);
return mongo;}
public CustomConversions customConversions() {return new CustomConversions(converters);
}
protected String getMappingBasePackage() {return "com.mckang.springdata.mongo";
}}
SpringConfig
Step3. “Configure Spring Beans”
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = { TestConfig.class })@Transactional@DirtiesContextpublic class TestMain {
@AutowiredCustomerRepository repository;
@AutowiredDataSource dataSource;
@Testpublic void testFindAll() {
List<Customer> results = repository.findAll();assertThat(results, is(notNullValue()));assertThat(results, hasSize(3));assertThat(results.get(0), notNullValue());assertThat(results.get(1), notNullValue());assertThat(results.get(2), notNullValue());
}
@Testpublic void testFindById() {
Customer result = repository.findOne(100L);assertThat(result, is(notNullValue()));assertThat(result.getFirstname(), is("John"));
}
@Testpublic void testFindByEmail() { Customer result = repository.findByEmailAddress(new EmailAddress("[email protected]"));}
TestCase@Testpublic void saveNewCustomer() {
Customer c = new Customer();//c.setFirstname("Sven");c.setLastname("Svensson");c.setEmailAddress(new EmailAddress("[email protected]"));Address a = new Address("Storgaten 6", "Trosa", "Sweden");c.add(a);repository.save(c);
}
@Test(expected = DataIntegrityViolationException.class)public void saveNewCustomerWithDuplicateEmail() {
Customer c = new Customer("Bob","Doe");c.setEmailAddress(new EmailAddress("[email protected]"));Address a = new Address("66 Main St", "Middletown", "USA");c.add(a);repository.save(c);
}
@Testpublic void deleteCustomer() {
Customer c = repository.findOne(100L);repository.delete(c);
}
}
Step4. “Test it…”(Need Fixture…)
Graph Database
Neo4j is the leading implementation of a property graph database. It is written predominantly in
Java and leverages a custom storage format and the facilities of the Java Transaction Architecture
(JTA) to provide XA transactions.
Neo4j integrates a transactional, pluggable indexing subsystem that uses Lucene as the
default. The index is used primarily to locate starting points for traversals. Its second use is to
support unique entity creation.
Cypher statement
With the declarative Cypher query language, Neo4j makes it easier to get started for everyone
who knows SQL from working with relational databases.
enabling users to define sophisticated queries like “find me all the customers who have friends
who have recently bought similar products.”
Like other query languages, it supports filtering, grouping, and paging. Cypher allows easy
creation, deletion, update, and graph construction.
Graph Model Domain Model
<<NodeEntity>
>
Customer
<<NodeEntity>
>
Address
EmailAddress
AbstractEntity
Step1. “Define Domain Model”
Customer
Address
Address
AbstractDocumentpublic abstract class AbstractEntity {
@GraphIdprivate Long id;
public Long getId() {return id;
}
@Overridepublic boolean equals(Object obj) {
if (this == obj) {return true;
}
if (id == null || obj == null || !getClass().equals(obj.getClass())) { return false;} return id.equals(((AbstractEntity) obj).id);
}
@Overridepublic int hashCode() {
return id == null ? 0 : id.hashCode();}
}
Customer@NodeEntitypublic class Customer extends AbstractEntity {
private String firstName, lastName;
@Indexed(unique = true)private String emailAddress;
@RelatedTo(type = "ADDRESS")private Set<Address> addresses = new HashSet<Address>();
public Customer(String firstName, String lastName, String emailAddress) {
Assert.hasText(firstName); Assert.hasText(lastName); Assert.hasText(emailAddress);
this.firstName = firstName; this.lastName = lastName; this.emailAddress = emailAddress;}
protected Customer() {
} …
Step1. “Define Domain Model”
Address@NodeEntitypublic class Address extends AbstractEntity {
private String street, city;
public Address(String street, String city) {this.street = street;this.city = city;
}
public Address() {
} …
EmailAddresspublic class EmailAddress {
private final String value;
public EmailAddress(String emailAddress) {Assert.isTrue(isValid(emailAddress), "Invalid email address!");this.value = emailAddress;
}
protected EmailAddress() {
}…
public interface CustomerRepository extends GraphRepository<Customer> {
Customer findOne(Long id);
<C extends Customer> C save(C customer);
Customer findByEmailAddress(String emailAddress);}
CustomerRepository
Step1. “Define Domain Model”
Step2. “Define Repository Interface”
-Just only interface
@Configuration@ComponentScan@ImportResource("classpath:META-INF/spring/spring-data-context.xml")@EnableTransactionManagementclass ApplicationConfig {
@Bean(destroyMethod = "shutdown")public GraphDatabaseService graphDatabaseService() {
//return new EmbeddedGraphDatabase("target/graph.db");return new SpringRestGraphDatabase("http://localhost:7474/db/data");
}}
SpringConfig
Step3. “Configure Spring Beans”
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:neo4j="http://www.springframework.org/schema/data/neo4j" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/data/neo4j http://www.springframework.org/schema/data/neo4j/spring-neo4j-2.0.xsd">
<!--neo4j:config storeDirectory="target/graph.db" /--><neo4j:config graphDatabaseService="graphDatabaseService" /><neo4j:repositories base-package="com.mckang.springdata.neo4j" />
</beans>
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = { TestConfig.class })public class TestMain {
@AutowiredCustomerRepository repository;
@AutowiredDataSource dataSource;
@Testpublic void savesCustomerCorrectly() {
EmailAddress email = new EmailAddress("[email protected]");Customer alicia = new Customer("Alicia", "Keys",email.getEmail()); // todoalicia.add(new Address("27 Broadway", "New York"));Customer result = repository.save(alicia);assertThat(result.getId(), is(notNullValue()));}
@Testpublic void readsCustomerByEmail() {
EmailAddress email = new EmailAddress("[email protected]");Customer alicia = new Customer("Alicia", "Keys",email.getEmail());repository.save(alicia);Customer result = repository.findByEmailAddress(email.getEmail());assertThat(result, is(alicia));}
TestCase
@Testpublic void preventsDuplicateEmail() {
final EmailAddress email = new EmailAddress("[email protected]"); Customer dave = repository.findByEmailAddress(email.getEmail()); Customer anotherDave = new Customer("Dave", "Matthews",dave.getEmailAddress()); repository.save(anotherDave);}
}
Step4. “Test it…”(Need Fixture…)