#298 - Add JPA example for composable repositories.
This commit is contained in:
committed by
Oliver Gierke
parent
a0775f774b
commit
5c01c44130
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2017 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.compositions;
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* Sample configuration to bootstrap Spring Data JPA through JavaConfig
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@SpringBootApplication
|
||||
class CompositeRepositoryConfig {}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2017 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.compositions;
|
||||
|
||||
/**
|
||||
* Entity aspect containing firstname and lastname properties.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public interface Contact {
|
||||
|
||||
/**
|
||||
* @return the first name.
|
||||
*/
|
||||
String getFirstname();
|
||||
|
||||
/**
|
||||
* @return the last name.
|
||||
*/
|
||||
String getLastname();
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2017 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.compositions;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Repository fragment for {@link Contact}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public interface ContactRepository {
|
||||
|
||||
/**
|
||||
* Find relatives of this {@link Contact}.
|
||||
*
|
||||
* @param contact must not be {@literal null}.
|
||||
* @return {@link List} of relatives.
|
||||
*/
|
||||
List<Contact> findRelatives(Contact contact);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2017 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.compositions;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
/**
|
||||
* {@link ContactRepository} fragment implementation.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class ContactRepositoryImpl implements ContactRepository {
|
||||
|
||||
final @NonNull EntityManager entityManager;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see example.springdata.jpa.compositions.ContactRepository#findRelatives(example.springdata.jpa.compositions.Contact)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<Contact> findRelatives(Contact contact) {
|
||||
|
||||
return entityManager.createQuery("SELECT u FROM User u WHERE u.lastname = :lastname") //
|
||||
.setParameter("lastname", contact.getLastname()) //
|
||||
.getResultList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2017 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.compositions;
|
||||
|
||||
/**
|
||||
* Aspect interface for employee entities.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public interface Employee {
|
||||
|
||||
/**
|
||||
* @return the {@link Employee manager} object for this {@link Employee}.
|
||||
*/
|
||||
Employee getManager();
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2017 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.compositions;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Repository extension for {@link Employee} entities.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public interface EmployeeRepository {
|
||||
|
||||
/**
|
||||
* @param employee
|
||||
* @return list of co-workers having the same {@link Employee#getManager() manager}.
|
||||
*/
|
||||
List<Employee> findCoworkers(Employee employee);
|
||||
|
||||
/**
|
||||
* @param employee
|
||||
* @return list of subordinates for this {@link Employee manager}.
|
||||
*/
|
||||
List<Employee> findSubordinates(Employee manager);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2017 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.compositions;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
/**
|
||||
* {@link EmployeeRepository} fragment implementation.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class EmployeeRepositoryImpl implements EmployeeRepository {
|
||||
|
||||
final @NonNull EntityManager entityManager;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see example.springdata.jpa.compositions.EmployeeRepository#findCoworkers(example.springdata.jpa.compositions.Employee)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<Employee> findCoworkers(Employee employee) {
|
||||
|
||||
return entityManager.createQuery("SELECT u from User u where u.manager = :manager") //
|
||||
.setParameter("manager", employee.getManager()) //
|
||||
.getResultList();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see example.springdata.jpa.compositions.EmployeeRepository#findSubordinates(example.springdata.jpa.compositions.Employee)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<Employee> findSubordinates(Employee manager) {
|
||||
|
||||
return entityManager.createQuery("SELECT u from User u where u.manager = :manager") //
|
||||
.setParameter("manager", manager) //
|
||||
.getResultList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2017 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.compositions;
|
||||
|
||||
/**
|
||||
* Generic repository extension for JPA repositories to flush after each save.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public interface FlushOnSaveRepository<T> {
|
||||
|
||||
/**
|
||||
* Saves a given entity and flush immediately. Use the returned instance for further operations as the save operation
|
||||
* might have changed the entity instance completely.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @return the saved entity will never be {@literal null}.
|
||||
*/
|
||||
<S extends T> S save(S entity);
|
||||
|
||||
/**
|
||||
* Saves all given entities and flush after the save.
|
||||
*
|
||||
* @param entities must not be {@literal null}.
|
||||
* @return the saved entities will never be {@literal null}.
|
||||
* @throws IllegalArgumentException in case the given entity is {@literal null}.
|
||||
*/
|
||||
<S extends T> Iterable<S> saveAll(Iterable<S> entities);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2017 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.compositions;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
import org.springframework.data.jpa.domain.AbstractPersistable;
|
||||
import org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation;
|
||||
import org.springframework.data.jpa.repository.support.JpaPersistableEntityInformation;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Generic repository extension for JPA repositories to flush after each save.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class FlushOnSaveRepositoryImpl<T> implements FlushOnSaveRepository<T> {
|
||||
|
||||
final @NonNull EntityManager entityManager;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see example.springdata.jpa.compositions.FlushOnSaveRepository#save(java.lang.Object)
|
||||
*/
|
||||
@Transactional
|
||||
@Override
|
||||
public <S extends T> S save(S entity) {
|
||||
|
||||
doSave(entity);
|
||||
|
||||
entityManager.flush();
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param entity
|
||||
*/
|
||||
private <S extends T> void doSave(S entity) {
|
||||
|
||||
EntityInformation<Object, S> entityInformation = getEntityInformation(entity);
|
||||
|
||||
if (entityInformation.isNew(entity)) {
|
||||
entityManager.persist(entity);
|
||||
} else {
|
||||
entityManager.merge(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see example.springdata.jpa.compositions.FlushOnSaveRepository#saveAll(java.lang.Iterable)
|
||||
*/
|
||||
@Transactional
|
||||
@Override
|
||||
public <S extends T> Iterable<S> saveAll(Iterable<S> entities) {
|
||||
|
||||
entities.forEach(this::doSave);
|
||||
|
||||
entityManager.flush();
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private <S extends T> EntityInformation<Object, S> getEntityInformation(S entity) {
|
||||
|
||||
Class<?> userClass = ClassUtils.getUserClass(entity.getClass());
|
||||
|
||||
if (entity instanceof AbstractPersistable<?>) {
|
||||
return new JpaPersistableEntityInformation(userClass, entityManager.getMetamodel());
|
||||
}
|
||||
|
||||
return new JpaMetamodelEntityInformation(userClass, entityManager.getMetamodel());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2017 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.compositions;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.ManyToOne;
|
||||
|
||||
import org.springframework.data.jpa.domain.AbstractPersistable;
|
||||
|
||||
/**
|
||||
* Sample user class.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Entity
|
||||
@Getter
|
||||
@Setter
|
||||
public class User extends AbstractPersistable<Long> implements Contact, Employee {
|
||||
|
||||
@Column(unique = true) private String username;
|
||||
|
||||
private String firstname;
|
||||
private String lastname;
|
||||
|
||||
@ManyToOne private User manager;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2017 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.compositions;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
||||
* Composite repository implementing a default {@link CrudRepository} extended with {@link ContactRepository},
|
||||
* {@link EmployeeRepository}, and {@link FlushOnSaveRepository} fragments.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public interface UserRepository
|
||||
extends CrudRepository<User, Long>, ContactRepository, EmployeeRepository, FlushOnSaveRepository<User> {}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright 2017 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.compositions;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Integration test showing the usage of a composite repository via {@link UserRepository}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@Transactional
|
||||
@SpringBootTest
|
||||
public class ComposedRepositoryTests {
|
||||
|
||||
@Autowired UserRepository repository;
|
||||
|
||||
/**
|
||||
* Tests inserting a user and asserts it can be loaded again.
|
||||
*/
|
||||
@Test
|
||||
public void testInsert() {
|
||||
|
||||
User user = new User();
|
||||
user.setUsername("username");
|
||||
|
||||
user = repository.save(user);
|
||||
|
||||
assertThat(repository.findById(user.getId())).hasValue(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Testing {@link ContactRepository} fragment.
|
||||
*/
|
||||
@Test
|
||||
public void testContactRepository() {
|
||||
|
||||
User walter = new User();
|
||||
walter.setUsername("heisenberg");
|
||||
walter.setFirstname("Walter");
|
||||
walter.setLastname("White");
|
||||
|
||||
User walterJr = new User();
|
||||
walterJr.setUsername("flynn");
|
||||
walterJr.setFirstname("Walter Jr.");
|
||||
walterJr.setLastname("White");
|
||||
|
||||
repository.saveAll(Arrays.asList(walter, walterJr));
|
||||
|
||||
assertThat(repository.findRelatives(walter)).contains(walterJr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Testing {@link EmployeeRepository} fragment.
|
||||
*/
|
||||
@Test
|
||||
public void testFindCoworkers() {
|
||||
|
||||
User gustavo = new User();
|
||||
gustavo.setUsername("pollosh");
|
||||
gustavo.setFirstname("Gustavo");
|
||||
gustavo.setLastname("Fring");
|
||||
|
||||
User walter = new User();
|
||||
walter.setUsername("heisenberg");
|
||||
walter.setFirstname("Walter");
|
||||
walter.setLastname("White");
|
||||
walter.setManager(gustavo);
|
||||
|
||||
User jesse = new User();
|
||||
jesse.setUsername("capncook");
|
||||
jesse.setFirstname("Jesse");
|
||||
jesse.setLastname("Pinkman");
|
||||
jesse.setManager(gustavo);
|
||||
|
||||
repository.saveAll(Arrays.asList(gustavo, walter, jesse));
|
||||
|
||||
assertThat(repository.findCoworkers(walter)).contains(jesse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Testing {@link EmployeeRepository} fragment.
|
||||
*/
|
||||
@Test
|
||||
public void testFindSubordinates() {
|
||||
|
||||
User gustavo = new User();
|
||||
gustavo.setUsername("pollosh");
|
||||
gustavo.setFirstname("Gustavo");
|
||||
gustavo.setLastname("Fring");
|
||||
|
||||
User walter = new User();
|
||||
walter.setUsername("heisenberg");
|
||||
walter.setFirstname("Walter");
|
||||
walter.setLastname("White");
|
||||
walter.setManager(gustavo);
|
||||
|
||||
User jesse = new User();
|
||||
jesse.setUsername("capncook");
|
||||
jesse.setFirstname("Jesse");
|
||||
jesse.setLastname("Pinkman");
|
||||
jesse.setManager(gustavo);
|
||||
|
||||
repository.saveAll(Arrays.asList(gustavo, walter, jesse));
|
||||
|
||||
assertThat(repository.findSubordinates(gustavo)).contains(walter, jesse);
|
||||
assertThat(repository.findSubordinates(walter)).isEmpty();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user