How to Enable/Disable Entity Relations in Runtime? - spring-boot

I have a Spring Boot app that has basic CRUD services. For the read services I want to see their relations as well. There is no problem for implementing relations by #ManyToOne, #OneToOne, etc. annotations like this example.
My problem is I want to enable this relations based on a parameter in list service or I could use another endpoint as well. How can I achieve this? Any suggestions are welcome.
parameter version could be like ->
/employe/list?includeRelations=true
endpoint version could be like ->
/employee/list/byRelations
My entities are like;
#Entity
#Table(name = "employee")
public class Employee{
private long id;
private String name;
private Address address;
// getter setters
}
#Entity
#Table(name = "address")
public class Address {
private long id;
private String name;
private String postalCode;
// getter setters
}
EDIT
e.g.
without includeRelations=true '/employee/list' service should return this;
{
"id": 1,
"name": "Jane"
}
with includeRelations=true '/employee/list' service should return this;
{
"id": 1,
"name": "Jane"
"address": {
"id":1,
"name": "HOME",
"postalCode": "11111"
}
}

its some sudo code for your understanding . you can use Query Parameter and In Condition you call repo what you want :
for my scenario i want different response short, medium and long
#RequestMapping(value = "/getContacts", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, headers = "Accept=application/json")
public String getContact(#RequestBody ContactItemRequestInfo contactItemRequestInfo,
#RequestParam(required = false) String key,
String Contact)
{
if(key.equals("medium"))
{
return Contact="{\"responseCode\":\"02\",\"responseDescription\":\"Success\",\"totalCount\":2,\"contacts\":[{\"id\":114,\"firstName\":\"ali\",\"lastName\":\"kamran\"},{\"id\":115,\"firstName\":\"usman\",\"lastName\":\"khan\",\"middleName\":\"saad\"}]}";
}
else if(key.equals("long"))
{
return Contact="{\"responseCode\":\"03\",\"responseDescription\":\"Success\",\"totalCount\":2,\"contacts\":[{\"id\":114,\"firstName\":\"ali\",\"lastName\":\"kamran\"},{\"id\":115,\"firstName\":\"usman\",\"lastName\":\"khan\",\"middleName\":\"saad\"}]}";
}
else
{
return Contact="{\"responseCode\":\"00\",\"responseDescription\":\"Success\",\"totalCount\":2,\"contacts\":[{\"id\":114,\"firstName\":\"ali\",\"lastName\":\"kamran\"},{\"id\":115,\"firstName\":\"usman\",\"lastName\":\"khan\",\"middleName\":\"saad\"}]}";
}
}
It will be helpful for you !!

One of the ways would be to have different data transfer objects to return, depending on the REST request.
Let's assume you have the following classes, apart from entities.
class EmployeeDto {
private Long id;
private String name;
}
class EmployeeAddressDto {
private Long id;
private String name;
private AddressDto address;
}
class AddressDto {
private Long id;
private String name;
private int postalCode;
}
Then in a controller you would do something like this.
#GetMapping("/employee/list")
public ResponseEntity<?> getEmployees(#RequestParam int detailed) {
if (detailed) {
return employeeService.getDetailedEmployeeList();
} else {
return employeeService.getEmployeeList();
}
}
Service inteface would look like this.
interface EmployeService() {
List<EmployeeDto> getEmployeeList();
List<EmployeeAddressDto> getDetailedEmployeeList();
}
You would also need to handle entities to transfer objects conversions.

You can annotate the relations with fetchType=Lazy . Then invoke the getters to manually load the needed relations.
Another option is to eagerly load all relationships, but annotate the response with #JsonView and exclude the relations you don't need.

Related

Bidirectional #OneToOne Spring Data JPA, Hibernate

I am using Bidirectional #OneToOne from Hibernate documentation. I have created an identical model for the test.
I can't get Phone via PhoneDetails. I get an error - Message Request processing failed; nested exception is org.hibernate.LazyInitializationException: could not initialize proxy [com.example.model.Phone#1] - no Session.
I've tried many options and it doesn't work.
Please tell me how to get the Phone correctly? I sit all day trying to do this. I did not find any options on the Internet, so I ask here.
Phone.java
#Entity(name = "Phone")
public class Phone {
#Id
#GeneratedValue
private Long id;
#Column(name = "`number`")
private String number;
#OneToOne(mappedBy = "phone",
cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.LAZY)
private PhoneDetails details;
public Phone() {
}
public Phone(String number) {
this.number = number;
}
// Getters and setters are omitted for brevity
public void addDetails(PhoneDetails details) {
details.setPhone( this );
this.details = details;
}
public void removeDetails() {
if ( details != null ) {
details.setPhone( null );
this.details = null;
}
}
}
PhoneDetails.java
#Entity(name = "PhoneDetails")
public class PhoneDetails {
#Id
#GeneratedValue
private Long id;
private String provider;
private String technology;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "phone_id")
private Phone phone;
public PhoneDetails() {
}
public PhoneDetails(String provider, String technology) {
this.provider = provider;
this.technology = technology;
}
// Getters and setters are omitted for brevity
}
LifecycleController.java
#Controller
public class LifecycleController {
#Autowired
ServiceJpa serviceJpa;
#GetMapping(value = "/savePhoneAndPhoneDetails")
public String savePersonAddress () {
Phone phone = new Phone( "123-456-7890" );
PhoneDetails details = new PhoneDetails( "T-Mobile", "GSM" );
phone.addDetails( details );
serviceJpa.savPhone( phone );
return "/savePhoneAndPhoneDetails";
}
#GetMapping(value = "/getPhone")
public String addPersonAddress () {
PhoneDetails address = serviceJpa.findPhoneDetailsById(2L).orElseThrow();
Phone phone = address.getPhone();
/*
An error appears here -
could not initialize proxy
[com.example.model.Phone#1] - no Session
*/
System.out.println(phone.getNumber());
return "/getPhone";
}
}
ServiceJpa.java
#Service
#Transactional
public class ServiceJpa {
#Autowired
PhoneJpa phoneJpa;
#Autowired
PhoneDetailsJpa phoneDetailsJpa;
#Transactional
public void savPhone(Phone phone) {
phoneJpa.save(phone);
}
#Transactional
public Optional<PhoneDetails> findPhoneDetailsById(Long id) {
return phoneDetailsJpa.findById(id);
}
}
interface PhoneJpa.java
#Repository
public interface PhoneJpa extends JpaRepository<Phone, Long> {
}
interface PhoneDetailsJpa.java
#Repository
public interface PhoneDetailsJpa extends JpaRepository<PhoneDetails, Long> {
}
I agree with Andriy's comment with a slight addition of "You should not access [lazily loaded] entity details outside transaction bounds". But, for starters, is there some reason you want the OneToOne to be FetchType.LAZY to begin with? If you changed it to EAGER, your "lazy" problem would be resolved by virtue of it no longer being a lazy reference but being a real hydrated object.
If that is not the exact route you want to take, there are a dozen ways to EAGERLY fetch things in general and frankly too many to present a single solution here as best/ideal. As your code exists, since all the dereferencing (for now) is happening inside your Controller, then Andriy's suggestion to add #Transaction to the Controller may suffice in that it will be lazily fetched when you need it.
But in the future, if you have Lazy elements in a POJO that get returned to the stack higher than the controller, say, just before they are serialized to JSON for example, then even the CONTROLLER's #Transactional wouldn't be "high" enough in the stack and you'll end up with the same Lazy init problem..
Also, by having it be Lazy and then dereferencing it elsewhere, you're guaranteeing two trips to the Database. With proper FETCH/JOIN eager loads, you would limit that to one, which can be another performance benefit.
So either way, you're back to the real problem at hand.. looking for ways to ensure your operations occur ENTIRELY inside a Transaction boundary OR having to completely hydrate the object so no "Lazy" danglers get dereferenced outside of that.. i.e. by making them eager or by force-initializing any potential Lazy proxies/collections.

How to change spring resources list name

I have a controller returning all cars in my database. It is achieved by putting the car list into Resources(see the code). I want to be able to rename the list's name from 'carDTOList' to 'carList". How to do that?
public class CarDTO {
private String id;
private UserDTO owner;
private String brand;
private String model;
private String color;
private String plate;
private String additionals;
#GetMapping("/cars")
public ResponseEntity<?> getAllCars() {
List<Resource<CarDTO>> cars = StreamSupport.stream(repository.findAll().spliterator(), false)
.map(car -> assembler.toResource(modelMapper.map(car, CarDTO.class)))
.collect(Collectors.toList());
Resources<Resource<CarDTO>> carsResource = new Resources<Resource<CarDTO>>(cars, ControllerLinkBuilder
.linkTo(ControllerLinkBuilder.methodOn(CarController.class).getAllCars()).withSelfRel());
return ResponseEntity.ok(carsResource);
}
{
"_embedded": {
"carDTOList": [
{
"id": "5d5bc8144a8fb83fd42120e1",
"owner": {
"id": "5d5bc8144a8fb83fd42120de",
As you see in the response it is set to 'carDTOList'
You can use Spring annotation:
#org.springframework.hateoas.core.Relation(value = "resource", collectionRelation = "resources")
to annotate your DTO class. So now when you return one element it will be called resource. If you return list it will be called resources.
If you want to keep the CarDTO Java name, then take advantage of the Jackson annotation to change naming:
#JsonRootName("car")
public class CarDTO {
Based on the config you have, when a collection is returned then a List suffix will be added.. resulting in carList.
you can refactor (rename the file) CarDTO class to CarList. That should do it.

Relationship Exists in neo4j but not in Spring #NodeEntity

I have a class in my domain called Activity that looks like the following
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
#NodeEntity
public class Activity {
#GraphId
private Long id;
private String title;
private String description;
#Relationship(type = "RELATED_TO", direction = Relationship.UNDIRECTED)
private List<Activity> relatedActivities = new ArrayList<>();
public Activity() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public String getDescription() {
return description;
}
public Collection<Activity> getRelatedActivities() {
System.out.println("getting relatedActivities");
System.out.println(relatedActivities);
return relatedActivities;
}
public void addRelatedActivity(Activity activity) {
this.relatedActivities.add(activity);
}
}
I create relationships using the following repository class:
#RepositoryRestResource(collectionResourceRel = "relationships", path = "relationships")
public interface RelationshipRepository extends GraphRepository<Relationship> {
#Query("MATCH (a1:Activity), (a2:Activity) " +
"WHERE a1.title = {0} AND a2.title = {1}" +
"CREATE (a1)-[:RELATED_TO]->(a2)")
void addRelationship(String a1Title, String a2Title);
}
I have verified that this code works using the neo4j browser, which lets me see existing nodes and relationships between them. However, when I access getRelatedActivities() on an Activity object, it's always an empty array, even if that Activity has other Activity nodes related to it, clearly visible in neo4j.
How can I get the relatedActivites on an Activity to automatically populate based on its relationships correctly?
The problem in your code is that you define the "target" as an Activity here
#Relationship(type = "RELATED_TO", direction = Relationship.UNDIRECTED)
private List<Activity> relatedActivities = new ArrayList<>();
but you also have a RelationshipEntity class in your code base: Relationship with the same type RELATED_TO.
When OGM gets the result it tries to match every field but since it converts the relationship type RELATED_TO to the RelationshipEntity and not an Activity object, it does not fill the list in the Activity class.

No composite key property found for type error in Spring JPA2

I have an error in spring JPA
org.springframework.data.mapping.PropertyReferenceException: No property CompanyId found for type CompanyUserDetail!
#Embeddable
public class CompanyUserKey implements Serializable {
public CompanyUserKey() {
}
#Column(name = "company_id")
private UUID companyId;
#Column(name = "user_name")
private String userName;
public UUID getCompanyId() {
return companyId;
}
public void setCompanyId(UUID companyId) {
this.companyId = companyId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
#Entity
#Table(name = "company_user_detail")
public class CompanyUserDetail {
#EmbeddedId
CompanyUserKey companyUserkey;
public CompanyUserKey getCompanyUserkey() {
return companyUserkey;
}
public void setCompanyUserkey(CompanyUserKey companyUserkey) {
this.companyUserkey = companyUserkey;
}
}
I am trying to access below method Service layer
#Component
public interface CompanyUserRepository extends JpaRepository<CompanyUserDetail, CompanyUserKey> {
public List<CompanyUserDetail> findByCompanyId(UUID companyId);
}
How can I achieve this ?
Thanks
Since in java model your CompanyUserKey is a property in the CompanyUserDetail class, I believe you should use full path (companyUserkey.companyId) to reach companyId:
public List<CompanyUserDetail> findByCompanyUserkeyCompanyId(UUID companyId);
Also note that you have a naming inconsistency: field in CompanyUserDetail is named companyUserkey instead of companyUserKey.
Assuming you are not using spring-data-jpa's auto generated implementations, your method contents might look something like the following:
FROM CompanyUserDetail c WHERE c.companyUserKey.companyId = :companyId
Now simply provide that query to the EntityManager
entityManager.createQuery( queryString, CompanyUserDetail.class )
.setParameter( "companyId", companyId )
.getResultList();
The key points are:
Query uses a named bind parameter called :companyId (not the leading :).
Parameter values are bound in a secondary step using setParameter method variants.
createQuery uses a second argument to influence type safety so that the return value from getResultList is a List<CompanyUserDetail> just like you requested.
Looking at spring-data-jpa's implementation however, I suspect it could look like this:
public interface CustomerUserRepository
extends JpaRepository<CompanyUserDetail, CompanyUserKey> {
#Query("select c FROM CompanyUserDetail c WHERE c.companyUserKey.companyId = :companyId")
List<CompanyUserDetail> findByCompanyId(#Param("companyId") UUID companyId);
}

Store enum name, not value in database using EBean

I have this enum :
public enum DocumentTypes {
PDF("PDF Document"), JPG("Image files (JPG)"), DOC("Microsoft Word documents");
private final String displayName;
DocumentTypes(final String display) {
this.displayName = display;
}
#Override
public String toString() {
return this.displayName;
}
}
And a model like this :
#Entity
#Table(name = "documents")
public class Document extends Model {
#Id
public Long id;
#Constraints.Required
#Formats.NonEmpty
#Enumerated(EnumType.STRING)
#Column(length=20, nullable=false)
public DocumentTypes type;
#Constraints.Required
#Formats.NonEmpty
#Column(nullable=false)
public String document;
}
I match the enum using this in my controller :
DynamicForm form = form().bindFromRequest();
// ...
Document doc = new Document();
doc.type = DocumentTypes.valueOf(form.field("type").value());
doc.save();
The problem is that in database, it's stored as "Microsoft Word documents", but I would prefer to store it as DOC.
How can I do that?
You can define it very fine granular with the Anotation EnumMapping or EnumValue. This works with the old version org.avaje.ebean.
It seems that there was a complete rewrite of the code. In the actual version there is a different approach.

Resources