Hibernate Spring repository inheritance add more details to an Entity - spring

I'm using hibernate and spring repository. I have 2 classes Person and PersonDetails which contains more details about the person.
#Entity
#Table(name = "person")
#Inheritance(strategy = InheritanceType.JOINED)
public class Person{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
protected Long id;
//name,birthdate ...
}
#Entity
#Table(name = "person_details")
#PrimaryKeyJoinColumn(name="id")
public class PersonDetails extends Person{
// private details accessible only for authorized user
}
When I create a Person I can't associate a PersonDetails it creates automatically a new instance of PersonDetails and add a new line in Person table.
Here's my repositories.
#NoRepositoryBean
public interface PersonBaseRepository<T extends Person> extends JpaRepository<T, Long> {
T findByNameAndFirstname(String name,String firstname);
}
#Transactional
public interface PersonRepository extends PersonBaseRepository<Person> {
}
#Transactional
public interface PersonDetailsRepository extends PersonBaseRepository<PersonDetails > {
}
To solve this I could instanciate my Entities only with PersonDetails but in some cases PersonDetails fields will be empty.
When I call findByNameAndFirstname from PersonDetailsRepository and the person isn't in person_details table but only in person I want to return the person matches in PersonDetails object.
Does anyone have a workable solution ? Thanks for your help.

Related

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);
}
}

(Spring/JpaRepository ) Dynamic #Query, when Inheriting methods of JpaRepository from BaseEntityRepository to SubEntityRepository

(This post continues the discussion from, the important parts are all repeated in-here:
(Spring/JpaRepository ) Inheriting methods of JpaRepository from BaseEntityRepository to SubEntityRepository)
Let's assume we have the following entities:
#Entity public class BaseEntity { }
#Entity public class SubEntity extends BaseEntity { }
and the following JpaRepository implemenations:
public interface BaseEntityRepository<T, I> extends JpaRepository<TableWithId, Long> {
#Query("SELECT t FROM BaseEntity t WHERE id = :id")
Optional<T> getById(#Param("id") Long id);
#Query("SELECT t FROM BaseEntity t WHERE customField = :customField")
List<T> findByCustomField(#Param("customField") String customField);
}
Now for the SubEntity I have another repo:
public interface SubEntityRepository extends BaseEntityRepository<SubEntity, Long> {}
Will JPA know that in #Query "BaseEntity" must be replaced by "SubEntity" and why?
If "no", how would be the best way to do what I want?
You need to use SPEL
#Query("SELECT T FROM #{#entityName} T WHERE T.id = :id")
Optional<T> getById(#Param("id") Long id);

Spring Jpa Entity - EntityManager.getReference

I have a Spring Boot application using Spring JPA, and what I'm trying to do is to save a new entity that has some foreign keys by just providing the IDs of those child entities. So, like:
#Table(name = "PERSON")
public class Person {
#Column(name = "PET_GUID")
public Pet pet;
}
Using this, I'd like to be able to have my PersonRepository that implements CrudRepository save a Person by just providing the guid of the Pet. Using straight up hibernate I can do that using EntityManager.getReference. I know that I can inject an EntityManager into my Entity or Repository and do something that way, but is there an easier way? I tried just doing person.setPet(new Pet(myPetsGuid)), but I get a "foreign key not found" when doing that, so that does not seem to work.
First, you should add #ManyToOne relation to the pet property:
#Entity
#Table(name = "PERSON")
public class Person {
//...
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "pet_guid")
privat Pet pet;
}
It says to Hibernate to use a foreign key to the Pet entity (and its table).
Second, you should use the method getOne of your PersonRepository to get a reference to the Pet entity, for example:
#Service
public class PersonService {
private final PetRepository petRepo;
private final PersonRepository personRepo;
//...
#NonNull
#Transactional
public Person create(#NonNull final PersonDto personDto) {
Person person = new Person();
//...
UUID petId = personDto.getPetId();
Pet pet = petRepo.getOne(perId);
person.setPet(pet);
//...
return person.save(person);
}
}

Spring Data JPA remove child entities

I have a load repository.
#Transactional
public interface MyLoadRepository extends CrudRepository<ParentEntity, Serializable> {
}
Then is my ParentEntity.
#MappedSuperclass
public class ParentEntity {
#Id
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name = "system-uuid", strategy = "uuid")
#Column(name = "id", unique = true)
private String uuid;
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
}
Then I have multiple child entities.
#Entity
#Table(name = "EntityA")
public class EntityA extends ParentEntity {
}
#Entity
#Table(name = "EntityB")
public class EntityB extends ParentEntity {
}
Ques : I want to delete these entities separately by my repository.
If I write something like this.
#Autowired
private MyLoadRepository repository;
and then repository.deleteAll()
I get error that repository is not entity (It obiviously not).
Here I want to delete either entityA or entityB data completely based on some condition. How can I do that ?
We should create repository per entity and not on non entity classes.
So, for your case you need 2 repository classes
#Transactional
public interface EntityARepo extends CrudRepository< EntityA, String> {
}
#Transactional
public interface EntityBRepo extends CrudRepository< EntityB, String> {
}
now in service classes you can do
#Autowired
private EntityARepo repoA;
#Autowired
private EntityBRepo repoB;
and then you can call delete method based on your condition
repoA.deleteAll()
or
repoB.deleteAll()
You need to fetch the entity based on a condition. For example, if the EntityA has a primary key uuid, then you must find EntityA by uuid and then delete the EntityA.
EntityA entityA = entityARepo.findOne(uuid);
repository.delete(entityA);
EntityB entityB = entityBRepo.findOne(uuid);
repository.delete(entityB);

How to get data from a table by entity class name using Spring Data JPA

I have a base entity class BaseDictionary:
#Entity
#Inheritance
public abstract class BaseDictionary {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long id;
#Column(name = "code")
protected String code;
#Column(name = "is_enabled")
protected Boolean enabled = true;
}
any child classes
#Entity
#Table(name = DB.DIC_RIGHTS_TYPE, schema = DB.SCHEMA_DIC)
public class DicRightsType extends BaseDictionary {}
#Entity
#Table(name = DB.DIC_ROLES_TYPE, schema = DB.SCHEMA_DIC)
public class DicRolesType extends BaseDictionary {}
There are many child classes like this.
Given an entity class name like DicRightsType I would like to get data from the table associated with the entity of that name. How is it possible to implement?
I wanted to use JPA repositories like this: Using generics in Spring Data JPA repositories but this does not suit my case because I only have the name of the entity class at runtime and do not know how to create a dynamic repository for the class.
You can write your own JpaRepository implementation to achieve this.
Step 1: A repository registry
class RepositoryRegistrar {
private static final Map<Class<T>, Repository<T, Serializable>> DICTIONARY = new HashMap<>();
public static void register(Class<T> entityClass, Repository<T, Serializable> repository) {
DICTIONARY.put(entityClass, repository);
}
public static Repository<T, Serializable> get(Class<T> entityClass) {
return DICTIONARY.get(entityClass);
}
}
Step 2: Custom JpaRepository implementation to populate the registry
#NoRepositoryBean
class RegisteringJpaRepository<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> {
public RegisteringJpaRepository(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {{
super(entityInformation, entityManager);
RepositoryRegistrar.register(entityInformation.getJavaType(), this);
}
}
You will have to tweak the configuration to use your custom implementation. Details for this are provided in the Spring Data JPA documentation.
Step 3: Obtain repository references from the registrar
Repository<?, ?> getRepository(String entityClassName) {
return RepositoryRegistrar.get(Class.forName(entityClassName));
}

Resources