Test if a collection is loaded in Spring DataJpaTest - spring-boot

I am having troubles testing whether a collection is loaded in Spring DataJpaTest.
For that I have created an entity that has id and list of items as seen below. The list should be lazy-loaded (so I am assuming it should not be loaded in the test).
#Data
#Entity
#Table(name = "examples")
public class Example {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long id;
#OneToMany(mappedBy = "example", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<ExampleElement> collection;
}
Repository code looks like this:
#Repository
public interface ExampleRepository extends PagingAndSortingRepository<Example, Long> {
#Query("SELECT e FROM Example e")
List<Example> findAllActive();
}
In the test I am creating a new Example entity, generating the collection, saving the entity to database and after that getting entities from database and checking whether the collections are initialized.
Test code looks like this:
#RunWith(SpringRunner.class)
#DataJpaTest
#AutoConfigureTestDatabase(replace = Replace.NONE)
public class ExampleRepositoryTest {
#Autowired
private ExampleRepository repository;
#Test
public void myTest() {
Example example = new Example();
example.setCollection(Utils.generateCollection()))
repository.save(example);
List<Example> actives = repository.findAllActive();
// tests in a loop whether the collection is initialized which should return false
}
I have tried the following:
Injecting TestEntityManager and getting the PersistenceUnitUtil from there using em.getEntityManager().getEntityManagerFactory().getPersistenceUnitUtil() and calling the isLoaded(actives.get(0).getCollection()) and isLoaded(actives.get(0), "collection") methods - both return true
Calling Hibernate.isInitialized(actives.get(0).getCollection()) and Hibernate.isPropertyInitialized(actives.get(0), "collection") methods, which both return true as well.
Checking what the collection contains - it is a PersistentBag containing the elements. I expect it to be null instead.
What am I missing?

Related

Why does Spring MVC controller method ignore FetchType.LAZY behaviour and act like FetchType.EAGER?

My Film model explicitly states it should fetch its children Actors lazily.
#Entity
#Getter
#Setter
public class Film {
...
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "film_actor",
joinColumns = #JoinColumn(name = "film_id"),
inverseJoinColumns = #JoinColumn(name = "actor_id")
)
private List<Actor> cast = new ArrayList<>();
This actually works perfectly when using the service/repository in any other context:
#Override
public void run(String... args) throws Exception {
List<Film> films = filmService.getAllFilms();
System.out.println(films);
}
But then for some mysterious reason, ONLY when used in a Spring MVC controller method (using typical annotations like #Controller and #RequestMapping), it always comes back eagerly loaded...Why and how do I change this? If I have 1000 films I want to display at once, I don't want to load in a million actors!
#GetMapping("")
public String filmsPage(Model model){
List<Film> allMyFilms = filmService.getAllFilms();
model.addAttribute("films", allMyFilms);
return "film/film-list";
}
Incidentally, for completeness, here are my service/repository layers:
#Service
#RequiredArgsConstructor
public class FilmServiceImpl implements FilmService {
private final FilmRepository filmRepo;
...
#Override
public List<Film> getAllFilms() {
List<Film> films = filmRepo.findAll();
return films;
}
Repository layer:
#Repository
public interface FilmRepository extends JpaRepository<Film, Long> {
List<Film> findAll();
}
How do you verify that the association is eager? Spring MVC has something enabled by default which is called "open session in view", which allows lazy loading until the request is finished. If you "check" whether data is loaded through your debugger, the debugger will invoke the toString method of PersistentBag which will initialize the lazy collection.

CascadeType Merge is ignored when Persist is set

Hy all
I'm having a hard time solving the following spring jpa problem.
Let's say I have the following simple data model (two entities with a one direction relationship between the two)
#Accessors(chain = true) #Getter #Setter #NoArgsConstructor #AllArgsConstructor
#MappedSuperclass
public class AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Version
private Long version;
}
#Accessors(chain = true) #Getter #Setter #NoArgsConstructor #AllArgsConstructor
#Entity
public class Entity1 extends AbstractEntity {
private String name;
}
#Accessors(chain = true) #Getter #Setter #NoArgsConstructor #AllArgsConstructor
#Entity
public class Entity2 extends AbstractEntity {
private String name;
#ManyToOne(cascade={ALL})
private Entity1 entity1;
}
and the following plumbing to store them
public interface Entity1Dao extends JpaRepository< Entity1, Long >, JpaSpecificationExecutor< Entity1 > {
Entity1 findByName(String name);
}
public interface Entity2Dao extends JpaRepository< Entity2, Long >, JpaSpecificationExecutor< Entity2 > {
Entity2 findByName(String name);
}
#Service
public class StoreService {
#Autowired
Entity1Dao dao1;
#Autowired
Entity2Dao dao2;
#Transactional
public Entity1 saveEntity1(Entity1 e) {
return dao1.save(e);
}
#Transactional
public Entity2 saveEntity2(Entity2 e) {
return dao2.save(e);
}
public Entity1 loadEntity1ByName(String name) {
return dao1.findByName(name);
}
}
#SpringBootApplication
public class JpaDemoApplication {
public static void main(String[] args) {
SpringApplication.run(JpaDemoApplication.class, args);
}
}
And the following test
#SpringBootTest
#TestMethodOrder(value = MethodOrderer.OrderAnnotation.class)
class JpaDemoApplicationTests {
#Autowired
StoreService store;
#Test
#Order(1)
void contextLoads() {
assertThat(store).isNotNull();
}
#Test
#Order(2)
void insertEntity1() {
store.saveEntity1(new Entity1("test entity1"));
Entity1 saved = store.loadEntity1ByName("test entity1");
assertThat(saved).isNotNull().hasNoNullFieldsOrProperties();
}
#Test
#Order(4)
void insertEntity2WithNewEntity1() {
store.saveEntity2(new Entity2("with new entity1", new Entity1("new entity1")));
}
#Test
#Order(5)
void insertEntity2WithExistingEntity1() {
store.saveEntity2(new Entity2("with saved entity1", store.loadEntity1ByName("test entity1")));
}
}
the last test (i.e. insertEntity2WithExistingEntity1) fails with the following exception
org.hibernate.PersistentObjectException: detached entity passed to
persist: com.example.jpaDemo.Entity1
If I change the CascadeType in Entity2 to MERGE, that test passes but the insertEntity2WithNewEntity1 fails with the following exception
org.hibernate.TransientPropertyValueException: object references an
unsaved transient instance - save the transient instance before
flushing : com.example.jpaDemo.Entity2.entity1 ->
com.example.jpaDemo.Entity1
I've tried multiple combination of cascading types bute it seems that as soon as PERSIST is used, the last test fails (and ALL includes PERSIST).
I would have expected that if MERGE and PERSIST are set, they would both be active but form the test it looks like MERGE is ignored when PERSIST is set.
Any clues, tips, hints at what I'm doing wrong so that both tests run???
EDIT
The tests are suppose to mimick the behaviour of a REST service endpoint reveiving and saving json reprensentation of an Entity1.
The json for the third test would be
{ name: "with new entity1", entity1: { name: "new entity1"}}
The json for the fourth would be
{ name: "with new entity1", entity1: { id: 1, version: 0, name: "test entity1"}}
JPA should persists the entity1 in the third test because it's id is null but should merge the one in the fourth test because it's id is not null.
I am however unable to do both, it's either one or the other.
EDIT 2
I've modified Entity1 slightly to have a reference to the list of Entity2 associated to it and annotated it with #OneToMany and the same cascading type as in Entity2 and it's the same behavior.
When I set the cascading type to MERGE and only Merge, I'm able to save a new entity that has a reference with an existing one but I can't save a new entity with a reference to a new one.
When I set the cascading type to PERSIST (i.e PERSIST on its own, PERSIST and MERGE or ALL), it's the oppposit; I can save a new entity with a reference to anther new entity but I can't save a new entity with a reference to an already existing one.
So it's seem that when PERSIST is set, it overrides the behavior of MERGE. That, to me, is a bug. Is it not?
I've uploaded the source to github in case you want to experiment or take a look at it yourself. https://github.com/willix71/persistVsMerge.git
You need to add #Transactional on your last test. The entity loaded is detached as there is no outer transaction, you can't persist it.
#Test
#Order(5)
#Transactional
void insertEntity2WithExistingEntity1() {
store.saveEntity2(new Entity2("with saved entity1", store.loadEntity1ByName("test entity1")));
}
I'm not sure if this is relevant anymore, but the code below works as I would expect. Removing "cascade = CascadeType.PERSIST" will fail the persist test with "object references an unsaved transient instance".
I also noticed in your github repo that you are attempting to do cascading both from parent to child and child to parent. I think this is the root cause of your issues.
Entities:
#Entity
#Table(name = "users")
#Getter
#Setter
#NoArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
UUID id;
#ManyToOne(cascade = CascadeType.PERSIST)
Address address;
}
#Entity
#Getter
#Setter
#NoArgsConstructor
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
#OneToMany(mappedBy = "address")
List<User> user;
}
Repositories:
public interface UserRepository extends JpaRepository<User, UUID> {
}
public interface AddressRepository extends JpaRepository<Address, UUID> {
}
Tests:
#DataJpaTest
#Import(DataSourceConfig.class)
class UserRepositoryTest {
#Autowired
private UserRepository userRepository;
#Autowired
private AddressRepository addressRepository;
#Test
void testMerge() {
var address = new Address();
addressRepository.save(address);
var user = new User();
user.setAddress(address);
userRepository.save(user);
assertThat(userRepository.findAll()).contains(user);
assertThat(addressRepository.findAll()).contains(address);
}
#Test
void testPersist() {
var address = new Address();
var user = new User();
user.setAddress(address);
userRepository.save(user);
assertThat(userRepository.findAll()).contains(user);
assertThat(addressRepository.findAll()).contains(address);
}
}

How can I add a tenant condition to Spring Data JPA Default and Dervied Queries

I have a Springboot Application with Repositories having Spring Data JPA Queries like findOne, findAll and also derived ones like findByID or findByName etc.
What I want to achieve is multitenancy. All entities have an "account_id" column which holds the tenant.
How do I add a filter like "account_id" to all the queries metioned above without using derived queries that contains those name slike findIdAndAccountid (which would be findone)
#Repository
public interface CategoryRepository extends JpaRepository<Category, Long> {
Category findByName(String name);
}
Here's the corresponding entity
#Entity
#Table(name = "unit")
#Data
public class Unit {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
#Column(name = "account_id")
private Long account_id;
}
I know most people use schemas as tenant separation but that's impossible for me. Is there a way (I didn't find one) to add such a tenant filter condition on those queries without writing NamedQueries or using DerivedQueries. An elegeant solution like annotate the repository or entity or maybe the queries that all queries should add the additional filter "account_id"?
You can add Where clause on your Entity classes (Didnt had time to test )
#Entity
#Table(name = "unit")
#Data
#Where(clause = "account_id= :account_id")
public class Unit {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
#Column(name = "account_id")
private Long account_id;
}
Update and Solution
1. Create a Filter & FilterDef on the entity like so
#FilterDef(name="accountFilter", parameters=#ParamDef( name="accountId", type="long" ) )
#Filters( {
#Filter(name="accountFilter", condition=":accountId = account_id")
} )
public class Category {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
#Column(name = "account_id")
private Long account_id;
}
enable filtering in the controller by autowiring entitymanager, writing a method to enable the filter and activate the filter in #ModelAttribute for each request
#RestController
#RequestMapping(path = "/categories",produces = MediaType.APPLICATION_JSON_VALUE )
public class CategoryController {
private final CategoryRepository repository;
#Autowired
private EntityManager entityManager;
CategoryController(CategoryRepository repository) {
this.repository = repository;
}
private void activateFilter() {
Session session = entityManager.unwrap(Session.class);
Filter filter = session.enableFilter("accountFilter");
filter.setParameter("accountId", Long.valueOf(TenantContext.getCurrentTenant()));
}
#ModelAttribute
public void initFilter() {
activateFilter();
}
... your rest methods here
}

hibernate entity different implementation

I have a project with multiple implementation and an entity class Person.
In every implementation there is a different database, different table and different columns.
In the DAO layer and the business layer the code is the same.
How can I change only the persistence layer to have different implementation of Person entity class based on a profile and keep unchanged the rest of the code?
//I would like to change table and columns based on a profile
#Entity
#Table(name = "person")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
private String first_name;
private String last_name
//getters,setters
}
//I would like to keep DAO unchanged no matter the profile
public interface PersonDao {
public List<Person> listAll() throws Exception;
}
public class PersonDaoImpl implements PersonDao{
#Override
public List<Person> listAll() throws Exception{
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Person> criteria = criteriaBuilder.createQuery(Person.class);
Root<Person> root = criteria.from(Person.class);
...the rest of the code
}
}
Have a generic abstract Person and PersonDao which will be extended/implemented by other classes (e.g. MongoPerson, MysqlPersonDao, PersonV2... based on your requirements). But use only Person and PersonDao in your service layer.
Autowire with Spring using qualifiers and configurations

Play Framework + Spring Data JPA : LazyInitializationException

These are the following classes:
#Entity
public class Question {
#Id
public Long id;
public String name;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#JoinColumn(name = "OWNER_ID", referencedColumnName = "QUES_ID")
public List<Choice> choices = new ArrayList<>();
}
#Named
#Singleton
public interface QuestionRepository extends CrudRepository<Question , Long> {
Question findByName(String name);
}
And in the Controller file I have this following File
#Transactional
public Result getQuestion() {
List<Choices> list = this.questionRepository.findByName("name").choices;
list.size();
return ok();
}
list.size() in getQuestion() throws me a LazyInitializationException because there is not open sessions
I know that changing the fetch type to EAGER or using a JPQL query above the function definition in QuestionRepository might solve it, but there are part in my application where those wont help and I would require to lazy fetch.
How would make the entire code in getQuestion() function use a single session/transaction or even better my entire request to take place in an single session/transaction?
From Spring Data JPA reference documentation
4.7.1. Transactional query methods
To allow your query methods to be transactional simply use #Transactional at the repository interface
you define.
Example 100. Using #Transactional at query methods
#Transactional(readOnly = true)
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByLastname(String lastname);
#Modifying
#Transactional
#Query("delete from User u where u.active = false")
void deleteInactiveUsers();
}
Typically you will want the readOnly flag set to true as most of the query methods will only read data. In contrast to that deleteInactiveUsers() makes use of the #Modifying annotation and overrides the transaction configuration. Thus the method will be executed with readOnly flag set to false.
So just add #Transactional annotation to your repository interfaces.

Resources