Let's assume we have two entities A and B which have such a relation:
public class A {
#Id
private Integer id;
#OneToMany(mappedBy = "parent")
private Set<B> childs = new HashSet<>();
// getters and setters
}
public class B {
#Id
private Integer id;
#ManyToOne
private A parent;
// getters and setters
}
I'm having 2 repositories, one for each entity (ARepository and BRepository) and i'm using spring data rest starter so i've got generated rest api.
Let assume we have an instance of A, which has as id=12
In order to insert a B entity and link it to A i have to: POST the url http://localhost:8080/appContext/b/ and giving it a request body as follow: {"parent": "a/12"}
It works fine, but it is possible to have the same thing by posting http://localhost:8080/appContext/a/12/childs/ with the same request param except the "parent" JSON attribute ? as it's actually possible to fetch the childs of a parent using this last url.
Related
I have a monolith app where its models are joined to each others(OnetOne, ManyToMany..).
I was able to create the different Microservices, but I got stuck on how to transition these relationships into Microservices.
Here is my first Class:
#Entity
#Table
public class A {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String name;
#ManyToOne
#JoinColumn(name = "ID",referencedColumnName="ID")
private B b;
//getters and setters
}
#Entity
#Table
public class B{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String name;
//getters and setters
}
I also Created a microservice for A (controller,repository, service...) and a separate microservice for B.
I am trying to call the Class Model B from the microservice B. But I am not sure how to do it?
I also wonder if it is write to link two classes by joint in microservices or not ?
Thanks
The join relations such as #OneToOne or #ManyToMany are JPA specific and there is no straightforward way to make them work in microservice world.
In general, in microservice world you give up the ACID transactions for cross-service relations and replace them with BASE transactions (eventual consistency behaviour).
In your example, you can achieve this by implementing one of the following strategies.
Fetch the required entity using rest API from the other service.
As you divide your domain into different bounded contexts (services), you will eventually create two different stores with the following entities:
Service A
#Entity
#Table
public class A {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String name;
#Column(name = "B_REFERENCE_ID")
private Integer bId;
//getters and setters
}
And Service B:
#Entity
#Table
public class B{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String name;
#ElementCollection
private List<Integer> aIds;
//getters and setters
}
Then, you create your target DTO in the the service (example for service B):
public class BDto{
private int id;
private String name;
private List<ADto> aIds;
//getters and setters
}
Then, you need to fetch the dto you want to expose/consume yourself:
#RestController
public class BController {
private final WebClient webClient;
public BController() {
this.webClient = WebClient.builder()
.baseUrl(SERVICE_A_URL)
.build();
}
#GetMapping(path = "/{id}")
public Mono<BDto> getB(#PathVariable int id) {
Optional<B> bEntity = fetchBFromDatabase();
if (bEntity.isPresent()) {
var b = bEntity.get();
var aEntityIds = b.getaIds();
return webClient
.method(HttpMethod.GET)
.uri(GET_A_URL, aEntityIds)
.exchangeToFlux(response -> response.bodyToFlux(ADto.class))
.collect(Collectors.toList()).map(aDtos -> new BDto(b.getId(), b.getName(), aDtos));
}
return Mono.empty();
}
}
If you are unfimiliar with WebClient and reactive concepts, reference spring boot docs https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html
Of course, the good old RestTemplate can be used here as well.
In order to provide data consistency, you will need to implements eventing system with a message broker in between such as Kafka, Apache Pulsar or RabbitMQ.
As an alternative approach, you can keep both A and B entities in both microservices. In service A, you store only the information of B entity that is required in the service A domain and vice versa. In microservice world it is rare that you will require all the B data in service A.
Then, you can keep your join relations as they are in A and B services for fetching purposes.
Remember that you will still require only single source of truth. So, if data changes in service B, then you will need to update your B_ENTITY data in service A and vice versa. Thus, eventing system will still be required to properly updates states in both your services.
The topic of state management in microservices is a complex one, so I recommend to read more about it to get more comfortable with the topic:
https://phoenixnap.com/kb/acid-vs-base
https://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215
https://samnewman.io/books/building_microservices_2nd_edition/
Microservices are meant to be indepedent, so that if one service failed, it will not affect the others.
But if you are using multi-module structure, then add the module using the following code to your pom.xml:
<modules>
<module>module1</module>
<module>module2</module>
</modules>
but I am not sure this will work with Jpa.
I have JPA entity for department:
public class Department {
#Id
#Column(unique = true, nullable = false)
private long id;
#Basic
#Column
#Nationalized
private String name;
#Basic
#Column(length = 400)
#Nationalized
private String branch;
...
}
And REST repository
#RepositoryRestResource(collectionResourceRel = "departments", path = "departments")
public interface DepartmentRestRepository extends PagingAndSortingRepository<Department, Long> {
...
}
Entity's branch field is IDs of entity parent departments separated by space, e.g. 1 2 3.
When I query specific department via /api/departments/ID is it possible to attach to it field like parents with all parent entities queried?
I tryed to add getParents method to entity, but it obviously gave me unneeded parents queries with all entities recursively.
Update 2019.01.17:
As a workaround I splitted Department entity into Department and DepartmentWithParents. I added Department getParents() method to DepartmentWithParents entity and exposed REST API method that returns DepartmentWithParents.
Is there better way?
As a workaround I splitted Department entity into Department and DepartmentWithParents. I added Department getParents() method to DepartmentWithParents entity and exposed REST API method that returns DepartmentWithParents.
I'm developing a Spring Boot app with Spring data, JPA, Hibernate combination. Below is the scenario I'm struggling with where the expected behavior is to update only some child entities while the parent entity is being inserted as new.
Entity classes
#Entity
public class A {
#Id
private long id;
#ManyToOne
#JoinColumn (name = "B_ID")
#Cascade ( { CascadeType.ALL } )
private B b;
}
#Entity
#DynamicUpdate
public class B {
#Id
private long id;
#OneToMany (mappedBy = "b")
#Cascade ( { CascadeType.ALL } )
private Set<A> as;
#ManyToOne
#JoinColumn (name = "C_ID")
#Cascade ( { CascadeType.ALL } )
private C c;
}
#Entity
#DynamicUpdate
public class C {
#Id
private long id;
#OneToMany (mappedBy = "c")
#Cascade ( { CascadeType.ALL } )
private Set<B> bs;
#ManyToOne
#JoinColumn (name = "D_ID")
#Cascade ( { CascadeType.ALL } )
private D d;
#ManyToOne
#JoinColumn (name = "E_ID")
#Cascade ( { CascadeType.ALL } )
private E e;
}
#Entity
#DynamicUpdate
public class D {
#Id
private long id;
#OneToMany (mappedBy = "d")
#Cascade ( { CascadeType.ALL } )
private Set<C> cs;
}
#Entity
#DynamicUpdate
public class E {
#Id
private long id;
#OneToMany (mappedBy = "e")
#Cascade ( { CascadeType.ALL } )
private Set<C> cs;
}
Below are the steps I'm performing in the app:
Pull B from repo; if it exists, update some of its field values [Or] create new B with field values.
Create new instance of A and set B into A.
Persist A (by invoking JpaCrudRepository.save(A)).
Success part:
Everything works fine when B doesn't exist in repo already. Which means:
New instances of B (and C,D,E) are created,
This newly created B is set into A,
And A is persisted to repo successfully (a new row is inserted in all corresponding tables in DB/repo).
Failure part:
Now, when B already exists in repo, the existing B is pulled properly,
Some fields of B are updated while C, D, E are left untouched,
And this updated B is set into A.
But when trying to persist A now a Unique constraint violation is thrown on D and E.
All the entities are marked with following:
• ID as auto-generated column (used as PK implicitly),
• Mapping between entities using the ID column,
• CascadeType ALL wherever mappings like OneToMany, ManyToOne are applied,
• Dynamic update annotation.
So far what I could gather from around the web on this subject:
The JPA Repository doesn't have merge() or update() operations available explicitly, and the save() is supposed to cover those. And this save() operation works by calling merge() if the entity instance exists already Or by calling persist() if it is new. And digging further, the entity's ID column is being used to determine its existence in the repo. If this ID is null, entity is determined as new, and as existing if ID is not null.
So in my failure case above, as the existing B entity instance is being pulled from the repo, it already has a non-null ID.
So I'm not currently clear on what exactly I'm missing here.
I tried finding any matching solution online but couldn't arrive at one yet.
Can someone please help me identify what is wrong with my approach?
Found the issue and solution. As A and B were being created as new, subsequently C,D,E were all created as new which caused the violation. So to solve this I had to pull each existing entity of C,D,and E from the DB, and set them respectively/hierarchically inside the newly created B entity, before attempting to persist.
Basically, don't create new instance for an entity if it is nested within a parent entity which is about to be persisted.. and the goal is to update when a similar record already exists.
I'm using Spring JPA in my DAO layer. I have an entity Projet having inside an entity property Client:
Project.java
#Entity
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int projetId;
private String libelle;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name="client_id")
private Client client;
// ... constructors, getters & setters
}
Client.java
#Entity
public class Client {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int clientId;
private String denomination;
// ... constructors, getters & setters
}
in my DAO interface I have the following specifications:
ProjetDao.java
#Repository
#Transactional
public interface ProjetDao extends CrudRepository<Projet, Integer> {
#Transactional
public Projet findByLibelle(String libelle);
#Transactional
public Projet findByProjetId(int projetId);
}
My question is: How can I specify in my DAO interface a method that will return all clients distinct in List<Client>?
From the documentation and JIRA:
List<Project> findAllDistinctBy();
The query builder mechanism built into Spring Data repository infrastructure is useful for building constraining queries over entities of the repository. The mechanism strips the prefixes find…By, read…By, query…By, count…By, and get…By from the method and starts parsing the rest of it. The introducing clause can contain further expressions such as a Distinct to set a distinct flag on the query to be created. However, the first By acts as delimiter to indicate the start of the actual criteria. At a very basic level you can define conditions on entity properties and concatenate them with And and Or.
You are dealing with a one-to-one relationship, in this case I guess the list that you need is not really related to specific project, you just want a distinct list of clients.
You will need to create another repository (ClientRepository) for the Client entity and add a findAllDistinct method in this repository.
my problem can be broken down to this little example:
I have a entity class A and a entity class B. A has a List of B objects. Now there is always only one B relevant. So I do not want to load all B's of an A, only to access this one B (last inserted B inside a A).
The question: Can I manipulate an entity without an service, so that there is a #Transient variable, that is always the newest B? And also without saving the newest B separately in A. Is there a way to achieve this?
class B{
#Id
#GeneratedValue
private Long id;
#Column(nullable=false)
private String name;
#Column(nullable=false)
private Date created = new Date();
}
class A{
#Id
#GeneratedValue
private Long id;
#OneToMany
#OrderBy("created ASC")
private List<B> b;
#Transient
private B newestB; // Here should be only the newest B
}
Yes. Forget storing the newest B as a variable and instead simply add a getter for it:
#Transient
public B getNewestB() {
return b.get(b.size() -1);
}
This will solve your problem under the assumption that b is set to FetchType.EAGER. Fetching using b's getter and FetchType.LAZY may not be so straight forward as Spring may rely on an AOP proxy call to trigger the lazy load (you'd need to experiment).
However, I'd discourage both these approaches. You're effectively trying to fit business logic into your Entity. Why not keep your entity clean and perform this query using B's repository?
E.g.
public interface BRepository extends CrudRepository<B, Long> {
#Query(...) //query to get newest B for specified A
B getNewest(A a)
}