Unable to retrieve Spring HATEOAS embedded resource object in case of #ManytoMany relationship and lookup table with extra column - spring-boot

I am unable to retrieve embedded .I am using Spring boot ,spring data rest and spring JPA. I have 3 tables in data base
user
competency
user_competency (join/composite table with extra column)
User
#Entity
#Table(name = "\"user\"", schema = "public")
#JsonIdentityInfo(
generator = ObjectIdGenerators.IntSequenceGenerator.class,
property = "userId")
public class User implements java.io.Serializable {
private Long userId;
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id", unique = true, nullable = false)
public Long getUserId() {
return this.userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
private Set<UserCompetency> userCompetencies = new HashSet<UserCompetency>(0);
#OneToMany(fetch = FetchType.EAGER,cascade = {CascadeType.ALL}, mappedBy = "user")
public Set<UserCompetency> getUserCompetencies() {
return this.userCompetencies;
}
public void setUserCompetencies(Set<UserCompetency> userCompetencies) {
this.userCompetencies = userCompetencies;
}
}
**Competency**
#Entity
#Table(name = "competency", schema = "public")
#JsonIdentityInfo(
generator = ObjectIdGenerators.IntSequenceGenerator.class,
property = "competencyId")
public class Competency implements java.io.Serializable {
private Long competencyId;
private Set<UserCompetency> userCompetencies = new HashSet<UserCompetency>(0);
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "competency_id", unique = true, nullable = false)
public Long getCompetencyId() {
return this.competencyId;
}
public void setCompetencyId(Long competencyId) {
this.competencyId = competencyId;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "competency")
public Set<UserCompetency> getUserCompetencies() {
return this.userCompetencies;
}
public void setUserCompetencies(Set<UserCompetency> userCompetencies) {
this.userCompetencies = userCompetencies;
}
}
UserCompetency
#Entity
#Table(name = "user_competency", schema = "public")
#JsonIdentityInfo(
generator =ObjectIdGenerators.IntSequenceGenerator.class,
property = "id")
public class UserCompetency implements java.io.Serializable {
private UserCompetencyId id;
private Level level;
private User user;
private Competency competency;
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "competencyId", column = #Column(name = "competency_id", nullable = false)),
#AttributeOverride(name = "userId", column = #Column(name = "user_id", nullable = false)) })
public UserCompetencyId getId() {
return this.id;
}
public void setId(UserCompetencyId id) {
this.id = id;
}
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "level_id")
public Level getLevel() {
return this.level;
}
public void setLevel(Level level) {
this.level = level;
}
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id", nullable = false, insertable = false, updatable = false)
public User getUser() {
return this.user;
}
public void setUser(User user) {
this.user = user;
}
#ManyToOne(fetch = FetchType.EAGER,cascade=CascadeType.ALL)
#JoinColumn(name = "competency_id", nullable = false, insertable = false, updatable = false)
public Competency getCompetency() {
return this.competency;
}
public void setCompetency(Competency competency) {
this.competency = competency;
}
}
UserCompetencyId
#Embeddable
public class UserCompetencyId implements java.io.Serializable {
private Long competencyId;
private Long userId;
public UserCompetencyId() {
}
public UserCompetencyId(Long competencyId, Long userId) {
this.competencyId = competencyId;
this.userId = userId;
}
#Column(name = "competency_id", nullable = false)
public Long getCompetencyId() {
return this.competencyId;
}
public void setCompetencyId(Long competencyId) {
this.competencyId = competencyId;
}
#Column(name = "user_id", nullable = false)
public Long getUserId() {
return this.userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public boolean equals(Object other) {
if ((this == other))
return true;
if ((other == null))
return false;
if (!(other instanceof UserCompetencyId))
return false;
UserCompetencyId castOther = (UserCompetencyId) other;
return (this.getCompetencyId() == castOther.getCompetencyId()) && (this.getUserId() == castOther.getUserId());
}
}
UserCompetencyRepository
public interface UserCompetencyRepository extends JpaRepository<UserCompetency, UserCompetencyId> {
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>Demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>Demo</name>
<description>Demo api </description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-hal-browser</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-mockmvc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-webmvc</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
and I want to perform GET using URI,it return me embedded object and cannot get real value of objects attributes
GET http://localhost:8080/userCompetencies
How Can I get attribute values of User and Competency object where userId=8 Help is required
After implementing suggested Projection Issue still not resolved and here is screen shot

One way would be to use projections like for example:
#Projection(name = "edit" , types = Employee.class)
public interface EditEmployeeProjection {
String getFirstName();
String getLastName();
Set<Project> getProjects();
}
With this the project list will be embedded in the result for http://localhost:8080/api/employee/1?projection=edit
Projections would be used automatically if you add excerptProjection to you repository like described here: How to expose a complete tree structure with Spring Data REST and HATEOAS?
See for example here: https://shinesolutions.com/2015/04/15/spring-data-rest-and-projections/
EDITED
In you case a projection would look like:
#Projection(name = "edit" , types = UserCompetency.class)
public interface UserCompetencyProjection {
User getUser();
Competency getCompetency();
}
With http://localhost:8080/userCompetencies?projection=edit you would then see wanted result.
EDITED 2
The code I used:
Competency.class
#Entity
#Table(name = "competency", schema = "public")
#JsonIdentityInfo(
generator = ObjectIdGenerators.IntSequenceGenerator.class,
property = "competencyId")
public class Competency implements java.io.Serializable {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "competency_id", unique = true, nullable = false)
private Long competencyId;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "competency")
private List<UserCompetency> userCompetencies = new ArrayList<>();
UserCompetency.class
#Entity
#Table(name = "user_competency", schema = "public")
#JsonIdentityInfo(
generator = ObjectIdGenerators.IntSequenceGenerator.class,
property = "id")
public class UserCompetency implements java.io.Serializable {
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "competencyId", column = #Column(name = "competency_id", nullable = false)),
#AttributeOverride(name = "userId", column = #Column(name = "user_id", nullable = false)) })
private UserCompetencyId id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id", nullable = false, insertable = false, updatable = false)
private User user;
#ManyToOne(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
#JoinColumn(name = "competency_id", nullable = false, insertable = false, updatable = false)
private Competency competency;
UserCompetencyId.class
#Embeddable
public class UserCompetencyId implements java.io.Serializable {
#Column(name = "competency_id", nullable = false)
private Long competencyId;
#Column(name = "user_id", nullable = false)
private Long userId;
UserCompetencyRepository.class
#RepositoryRestResource(excerptProjection = UserCompetencyProjection.class)
public interface UserCompetencyRepository extends JpaRepository<UserCompetency, UserCompetencyId> {
After Implementing This ,In my case its not working to show desired jason [![enter image description here][2]][2]

It worked out ,actually i was missing annotation of #RepositoryRestResource(excerptProjection = UserCompetencyProjection.class)
on UserCompetencyRepository class now the output look like this I am skipping as it is output , and putting necessary output.

Related

Quarkus Reactive with Vert.x and Hibernate Reactive / java.lang.NullPointerException: Cannot store to object array because "this.loadedState" is null

i am trying to use quarkus reactive with vert.x and hibernate reactive.
this is my pom.xml:
<quarkus-plugin.version>1.12.2.Final</quarkus-plugin.version>
and
<quarkus.platform.version>1.12.2.Final</quarkus.platform.version>
with:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-reactive-mysql-client</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-web</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-reactive</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-reactive-pg-client</artifactId>
</dependency>
this is my application.properties file:
# postgres-configuration
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=partner_usr
quarkus.datasource.password=postgrespw
quarkus.datasource.reactive.url=vertx-reactive:postgres://localhost:3310/partnerdb
# test, but not working (schema's won't created)
quarkus.hibernate-orm.database.generation.create-schemas=true
# working (drop-and-create only on mysql, not on postgres)
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.log.sql=true
quarkus.http.cors=true
Then, i have following entities:
#Data
#MappedSuperclass
public abstract class IdEntity {
#Id
#SequenceGenerator(name = "entitySeq", sequenceName = "entitiy_id", allocationSize = 1, initialValue = 5)
#GeneratedValue(generator = "entitySeq", strategy = GenerationType.AUTO)
private Long id;
}
#Data
#Entity
#EqualsAndHashCode(callSuper = true)
public class Person extends IdEntity {
private String firstName;
private String lastName;
public Person() {
}
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Address personAddress;
}
#Data
#Entity
#EqualsAndHashCode(callSuper = true)
public class Address extends IdEntity {
private String street;
private String houseNumber;
private int postalCode;
private String city;
#OneToMany(orphanRemoval = true, mappedBy = "personAddress", fetch = FetchType.LAZY)
private List<Person> persons = new ArrayList<>();
public Address() {
}
}
Now, i am calling a reactive web-service with a reactive db access:
#Path("/person")
#ApplicationScoped
public class PersonResource {
#Inject
io.vertx.mutiny.pgclient.PgPool sqlClient;
#Inject
Mutiny.Session mutinySession;
#GET
//#Produces(MediaType.APPLICATION_JSON)
#Path("/list-persons")
#Route(path = "/list-persons", methods = HttpMethod.GET, produces = MediaType.APPLICATION_JSON)
#Transactional
public Multi<Person> listAllPersons() {
// return sqlClient.query("SELECT * FROM Person ORDER BY lastName ASC").execute()
// .onItem().transformToMulti(set -> Multi.createFrom().iterable(set))
// .onItem().transform(this::transformPersons);
return mutinySession.createQuery("SELECT f FROM Person f ORDER BY f.lastName")
.getResults().onItem().transform(this::transformObject);
}
private Person transformObject(Object f) {
return (Person)f;
}
private List<Object> transformPersons(Object f) {
final Person person = (PartnerMockEntity)f;
final List<Object> bogus = new ArrayList<>();
bogus.add(partner);
return bogus;
}
}
Exception:
Resulted in: com.fasterxml.jackson.databind.JsonMappingException: Cannot store to object array because "this.loadedState" is null (through reference chain: de.subito.model.Person["personAddress"]->de.subito.model.Address["person"])
I tried to use :
FetchType.EAGER on Address in Person
I removed the #OneToMany Relation in Address: this solves the error (yay), but the addresses won't be returned in the resulting json (id is existing, but the values are not fetched)
The questions is, how can i fetch in reactive those kind of relations without getting errors?
Or do i need a angular page in order to display this correctly?
Somehow i forgot about how fetchType.Lazy works.
Simply add a join fetch into the hql and everything works as expected.
SELECT p from Person p left join fetch p.personAddress
When using this query, there's no session/closed or any other exception thrown and the json result will be displayed as expected.
Additional note: in order to avoid recursive serialization, it is required to use the
#JsonManagedReference and #JsonBackReference
Annotations, depending on your needs to your relations.

Mapstruct does not use builders defined by Lombok

Solution:
I had to change the ordering of my mapstruct and lombok annotationProcessorPaths.
I had to place mapstruct above lombok, then it worked.
I updated the pom below to the working version, so there is no non-working-code in here.
I also converted the lombok version back to the current release and not using the edge-version.
Original Problem:
I have 2 more or less identical sets of classes (see example below)
one set are the DTOs of my API, which I want to have immutable, using Lombok's #Value and #Builder
one set are the entities that are going to be stored in the database. With Lombok's #Data
Initially I set the project up to use:
Lombok 1.18.12
Mapstruct 1.3.1
Java 11
Maven
I found the Lombok documentation explaining how to add the annotation-processor to the maven-plugin
https://projectlombok.org/setup/maven
But when executing I still get Error:(16,25) java: ClassX does not have an accessible parameterless constructor.
Searching for this message I found some 2 to 3 years of problems, but nothing up to date. Also I saw, that the issue was resolved for those posts.
In at least one of the posts it was mentioned, that it worked, when splitting the project into modules. And this worked for me as well. When I move the DTOs to another maven module, build them there and set the dependency it works, but this is definitely not the project-structure I want to have. Also since I might need to move my entities out as well and I don't want to create a new module for each Pojo-structure I'm creating.
I also found that post on the Lombok Edge version:
https://projectlombok.org/download-edge
The second point in the change-list is
BREAKING CHANGE: mapstruct users should now add a dependency to lombok-mapstruct-binding. This solves compiling modules with lombok (and mapstruct).
So I tried that as well.
I added the repository to my pom, added lombok-mapstruct-binding and set the lombok version to edge-SNAPSHOT
But even after a clean the compile step fails.
In between I changed my DTOs to use #Data as well, but I would like to change this back.
Finally here are some examples and details on the code.
DTOs
#Data
#AllArgsConstructor(access = AccessLevel.PROTECTED)
#NoArgsConstructor(access = AccessLevel.PROTECTED)
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = BDto.class, name = "b"),
#JsonSubTypes.Type(value = CDto.class, name = "c")
})
public abstract class ADto {
private long id;
private String type;
private Set<String> metadata;
private Set<String> tags;
}
#Data
#NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BDto extends ADto {
private String path;
#Builder
private BDto(long id, String path, Set<String> metadata, Set<String> tags) {
super(id, "b", metadata, tags);
this.path = path;
}
}
#Data
#NoArgsConstructor(access = AccessLevel.PRIVATE)
public class CDto extends ADto {
private String name;
private Set<A> collection;
#Builder
private CDto(long id, String name, Set<A> collection, Set<String> metadata, Set<String> tags) {
super(id, "c", metadata, tags);
this.collection = collection;
this.name = name;
}
}
Entities
#Entity
#Table
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "type")
#AllArgsConstructor
#NoArgsConstructor
#Getter
public abstract class A extends PanacheEntityBase {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected long id;
#Column(name = "type", insertable = false, updatable = false)
private String type;
/* ... */
}
#Entity
#DiscriminatorValue("b")
#NoArgsConstructor
#Getter
#ToString
public class B extends A {
public B(long id, String path, Set<String> metadata, Set<Tag> tags) {
super(id, "b", metadata, tags);
this.path = path;
}
public B(String path) {
super(0, "b", new HashSet<>(), new HashSet<>());
this.path = path;
}
#Column(name = "path")
#Setter
private String path;
}
#Entity
#DiscriminatorValue("c")
#NoArgsConstructor
#Getter
public class C extends A {
public C(long id, String name, List<A> collection, Set<String> metadata, Set<Tag> tags) {
super(id, "c", metadata, tags);
this.collection = collection;
this.name = name;
}
#Column(name = "name")
private String name;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "c_id")
#OrderBy("order")
List<A> collection;
}
Mappers
public interface AMapper {
default String tagToDto(Tag tag) {
return tag.getTag();
}
default Tag tagFromDto(String tag) {
return Tag.createIfNotExists(tag);
}
}
#Mapper()
public interface BMapper extends AMapper {
#Override
#Mapping(target = "tags",
qualifiedByName = "tagToDto")
BDto toDto(B b);
#Override
#Mapping(target = "tags",
qualifiedByName = "tagToEntity")
B toEntity(BDto b);
}
#Mapper()
public interface CMapper extends AMapper {
#Override
#Mapping(target = "tags",
qualifiedByName = "tagToDto")
CDto toDto(C b);
#Override
#Mapping(target = "tags",
qualifiedByName = "tagToEntity")
C toEntity(CDto b);
}
Pom
<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<artifactId>dummy</artifactId>
<groupId>dummy</groupId>
<version>0.1.0</version>
<packaging>pom</packaging>
<properties>
<compiler-plugin.version>3.8.1</compiler-plugin.version>
<maven.compiler.parameters>true</maven.compiler.parameters>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<lombok.version>1.18.12</lombok.version>
<mapstruct.version>1.3.1.Final</mapstruct.version>
</properties>
<repositories>
<repository>
<id>projectlombok.org</id>
<url>https://projectlombok.org/edge-releases</url>
</repository>
</repositories>
<dependencies>
<!-- other stuff -->
<!-- Tools -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<!-- <scope>provided</scope> -->
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${compiler-plugin.version}</version>
<configuration>
<annotationProcessorPaths>
<annotationProcessorPath>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</annotationProcessorPath>
<annotationProcessorPath>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</annotationProcessorPath>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
With lombok (1.18.18) and mapstruct (1.4.2.Final) everything worked after I:
added plugin lombok-mapstruct-binding
added lombok-mapstruct-binding to annotationProcessorPaths section of plugin maven-compiler-plugin
links:
github example pom.xml: https://github.com/mapstruct/mapstruct-examples/blob/master/mapstruct-lombok/pom.xml
from https://mapstruct.org/faq/ :
If you are using Lombok 1.18.16 or newer you also need to add lombok-mapstruct-binding in order to make Lombok and MapStruct work together.

How to handle a post request with a ManyToOne relatshionship on an entity with an embeddedId?

I have a rest service with a pom.xml define like this :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
I have also a Store entity :
#Data
#Entity
#FieldDefaults(level = AccessLevel.PRIVATE)
#Table(name = "magasin")
#Inheritance(strategy = InheritanceType.JOINED)
public class Store implements Serializable {
#EmbeddedId
StoreId storeId;
#Column(name = "nom")
String name;
#MapsId("businessUnitId")
#ManyToOne
#JoinColumn(name = "bu_id", referencedColumnName = "id")
BusinessUnit businessUnit;
The Embeddable StoreId :
#Data
#FieldDefaults(level = AccessLevel.PRIVATE)
#Embeddable
public class StoreId implements Serializable {
#Column(name = "id")
Long id;
#Column(name = "bu_id")
Long businessUnitId;
With the following backendIdConverter :
#Component
public class StoreIdConverter implements BackendIdConverter {
#Override
public Serializable fromRequestId(String id, Class<?> entity) {
if(id != null) {
String[] param = id.split("_");
if (entity.getTypeName().equals(Store.class.getTypeName())) {
StoreId storeId = new StoreId();
storeId.setId(Long.valueOf(param[0]));
storeId.setBusinessUnitId(Long.valueOf(param[1]));
return storeId;
}
}
return id;
}
#Override
public String toRequestId(Serializable id, Class<?> entity) {
if(entity.getTypeName().equals(Store.class.getTypeName())){
StoreId storeId = (StoreId)id;
return String.format("%s_%s", storeId.getId(), storeId.getBusinessUnitId());
}
return id.toString();
}
#Override
public boolean supports(Class<?> entity) {
return true;
}
At this point, i can perform request with the following url without any problems :
http://localhost:9001/stores/1_1
And the resource returned :
{
"storeId" : {
"id" : 11,
"businessUnitId" : 1
},
"name" : "TEST",
"_links" : {
"self" : {
"href" : "http://localhost:9001/stores/11_1"
},
"store" : {
"href" : "http://localhost:9001/stores/11_1"
},
"businessUnit" : {
"href" : "http://localhost:9001/stores/11_1/businessUnit"
}
}
}
But when i'am including a ManyToOne relationship to the store entity into another entity like this :
#Data
#FieldDefaults(level = AccessLevel.PRIVATE)
#Entity
#Table(name = "sms")
#SequenceGenerator(name = "SmsSeq", sequenceName = "sms_seq", allocationSize = 1)
#Inheritance(strategy = InheritanceType.JOINED)
public class Sms implements Serializable {
#Id
#GeneratedValue(generator = "SmsSeq", strategy = GenerationType.SEQUENCE)
#Column(name = "id", nullable = false)
Long id;
#Column(name = "message", nullable = false)
String message;
#ManyToOne(targetEntity = Store.class)
#JoinColumns({
#JoinColumn(name = "magasin_id", referencedColumnName = "id", nullable = false),
#JoinColumn(name = "bu_id", referencedColumnName = "bu_id", nullable = false)
})
Store store;
}
A Post request
http://localhost:9001/smses
{
"message": "Bonjour",
"store": "http://10.1.3.38:9001/stores/1_1"
}
throw this kind of exception :
RepositoryRestExceptionHandler : JSON parse error: No converter found capable of converting from type [java.lang.String] to type [com.test.model.StoreId]; nested exception is com.fasterxml.jackson.databind.JsonMappingException: No converter found capable of converting from type [java.lang.String] to type [com.test.model.StoreId] (through reference chain: com.test.model.Sms["store"])
I understand that the BackendIdConverter does not parse the store associated url, is there a way to achieve the same goal on a Request body ?

getOutputStream() has already been called for this response when fill data in child table with spring boot

I am using spring boot 1.5.10 & spring rest to develop some rest services
I have relation one to many between product and services.
When I fill data in services table and access the service that get me all products(http://localhost:8080/user/products) give me this exception:
Caused by: java.lang.IllegalStateException: getOutputStream() has
already been called for this response.
and repeated json appear in the browser!
If services table is empty: no exception is thrown. I don't know why.
I found a link that discuss the problem but yet I couldn't solve it.
Product entity:
#Entity
#Table(name = "products")
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "ID")
private Integer id;
#Basic(optional = false)
#Column(name = "NAME")
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "productId")
private List<Service> servicesList;
}
Service Entity:
#Entity
#Table(name = "services")
public class Service implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "ID")
private Integer id;
#Basic(optional = false)
#Column(name = "NAME")
private String name;
#Basic(optional = false)
#Column(name = "TYPE")
private int type;
#JoinColumn(name = "PRODUCT_ID", referencedColumnName = "ID")
#ManyToOne(optional = false)
private Product productId;
}
And the ProductController
#Controller
#RequestMapping("user")
#CrossOrigin(origins="http://localhost:4200", allowedHeaders="*")
public class ProductController {
#Autowired
private IProductService productService;
#GetMapping("product/{id}")
public ResponseEntity<Product> getProductById(#PathVariable("id") Integer id) {
Product product = productService.getProductById(id);
return new ResponseEntity<Product>(product, HttpStatus.OK);
}
#GetMapping("products")
public ResponseEntity<List<Product>> getAllProducts() {
List<Product> list = productService.getAllProducts();
return new ResponseEntity<List<Product>>(list, HttpStatus.OK);
}
}
pom.xml is defined as follows.
http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
<groupId>com.app</groupId>
<artifactId>Assignment</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Assignment</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
As discussed in the link you have shared, you need to create a response class to be sent to the client who is calling the API.
For example, you might have a class saying ProductResponse which might look like this.
public class ProductResponse implements Serializable {
public Integer id;
public String name;
public List<Service> servicesList;
}
Now in the controller class, populate create the response as follows.
#GetMapping("product/{id}")
public ResponseEntity<ProductResponse> getProductById(#PathVariable("id") Integer id) {
Product product = productService.getProductById(id);
ProductResponse productResponse = createProductResponse(product);
return new ResponseEntity<ProductResponse>(productResponse, HttpStatus.OK);
}
ProductResponse createProductResponse(Product product) {
ProductResponse productResponse = new ProductResponse();
productResponse.id = product.id;
productResponse.name = product.name;
productResponse.serviceList = product.serviceList;
}
And yes, you need to specify the FetchType.EAGER in the entity class of Product.
#Entity
#Table(name = "products")
public class Product implements Serializable {
// ... Other parameters
#OneToMany(fetch = "FetchType.EAGER", cascade = CascadeType.ALL, mappedBy = "productId")
private List<Service> servicesList;
}
Hope that helps.

CommandLine nashorn script (jjs) unable to create entity manager. Why?

CommandLine nashorn script (jjs) unable to create entity manager.
Why does this not work?
How can it be made to work (if at all)?
i.e.,
running the script looks like this...
$ jjs -cp ".;myjpaclasses-1.jar;" myNashornScript.js
i.e., where "myNashornScript.js" contains...
/* global Java, java */
print("begin test...");
var EntityManagerFactory = Java.type('javax.persistence.EntityManagerFactory');
var EntityManager = Java.type('javax.persistence.EntityManager');
var Persistence = Java.type('javax.persistence.Persistence');
var Employees = Java.type('aaa.bbb.ccc.jpa.Employees');
var employees = new Employees();
var javaImports = new JavaImporter(java.io, java.lang, java.util);
try
{
with (javaImports) {
var emf = Persistence.createEntityManagerFactory("hr_pu"); <== issue here(?)...
var em = emf.createEntityManager();
var query = em.createQuery(
"SELECT e FROM Employees e WHERE e.employeeId > ?1")
.setParameter(1, 100)
.setFirstResult(0);
var rows = query.getResultList();
//...print info on 2nd row object of returned list...
//...print returned list size...
print("rows.get(2).getFirstName()="+ rows.get(2).getFirstName()
+ "...returned row count=" + rows.size());
}
} catch (e) {
print(e.message);
}
print("end test...");
Running script from command line consistently yields the following...
begin test...
No Persistence provider for EntityManager named hr_pu
end test...
Note: fwiw, this script seems to work fine, when called from a java app...i.e.,
package aaa.bbb.ccc.jar;
import java.io.FileNotFoundException;
import java.io.FileReader;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class RunScript {
public static void main (String[] args) throws ScriptException, FileNotFoundException
{
ScriptEngineManager engineManager = new ScriptEngineManager();
ScriptEngine engine = engineManager.getEngineByName("nashorn");
engine.eval(new FileReader("src/main/resources/myNashornScript.js"));
}
}
...i.e., which yields...
begin test...
[EL Warning]: transaction: 2016-05-12 14:18:00.773--ServerSession(1829217853)--PersistenceUnitInfo hr_pu has transactionType RESOURCE_LOCAL and therefore jtaDataSource will be ignored
[EL Info]: 2016-05-12 14:18:00.78--ServerSession(1829217853)--EclipseLink, version: Eclipse Persistence Services - 2.6.3.v20160428-59c81c5
[EL Info]: connection: 2016-05-12 14:18:01.183--ServerSession(1829217853)--/file:/C:/tools/netbeansWS/myjpaclasses/target/classes/_hr_pu login successful
rows.get(2).getFirstName()=Alexander...returned row count=106
end test...
Thanks for any help/guidance!
P.S.
If it makes any difference, the persistence.xml (located in src/main/resources/META-INF of "myjpaclasses.jar") looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="hr_pu" transaction-type="RESOURCE_LOCAL">
<jta-data-source>jdbc/HR</jta-data-source>
<class>aaa.bbb.ccc.jpa.Regions</class>
<class>aaa.bbb.ccc.jpa.Employees</class>
<class>aaa.bbb.ccc.jpa.Departments</class>
<class>aaa.bbb.ccc.jpa.Locations</class>
<class>aaa.bbb.ccc.jpa.Jobs</class>
<class>aaa.bbb.ccc.jpa.Countries</class>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="javax.persistence.jdbc.driver" value="oracle.jdbc.OracleDriver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:oracle:thin:#localhost:1521:XE"/>
<property name="javax.persistence.jdbc.user" value="HR"/>
<property name="javax.persistence.jdbc.password" value="HR"/>
</properties>
</persistence-unit>
</persistence>
The JPA "Employee" class looks like this:
package aaa.bbb.ccc.jpa;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.UniqueConstraint;
import javax.xml.bind.annotation.*;
#XmlAccessorType(XmlAccessType.FIELD)
#Entity
#Table(name = "EMPLOYEES", uniqueConstraints = #UniqueConstraint(columnNames = {"EMAIL"}))
#XmlRootElement
public class Employees implements Serializable {
#Column(name = "LAST_NAME", table = "EMPLOYEES", nullable = false, length = 25)
#Basic
private String lastName;
#Column(name = "HIRE_DATE", table = "EMPLOYEES", nullable = false)
#Temporal(TemporalType.TIMESTAMP)
#Basic
private Date hireDate;
#ManyToOne(targetEntity = Departments.class)
#JoinColumn(name = "DEPARTMENT_ID", referencedColumnName = "DEPARTMENT_ID")
private Departments departmentId;
#Column(name = "EMPLOYEE_ID", table = "EMPLOYEES", nullable = false)
#Id
private Integer employeeId;
#ManyToOne(targetEntity = Employees.class)
#JoinColumn(name = "MANAGER_ID", referencedColumnName = "EMPLOYEE_ID")
private Employees managerId;
#Column(name = "SALARY", table = "EMPLOYEES", scale = 2, precision = 8)
#Basic
private BigDecimal salary;
#Column(name = "COMMISSION_PCT", table = "EMPLOYEES", scale = 2, precision = 2)
#Basic
private BigDecimal commissionPct;
#XmlTransient
#OneToMany(targetEntity = Employees.class, mappedBy = "managerId")
private List<Employees> employeesCollection;
#Column(name = "FIRST_NAME", table = "EMPLOYEES", length = 20)
#Basic
private String firstName;
#ManyToOne(optional = false, targetEntity = Jobs.class)
#JoinColumn(name = "JOB_ID", referencedColumnName = "JOB_ID")
private Jobs jobId;
#Column(name = "PHONE_NUMBER", table = "EMPLOYEES", length = 20)
#Basic
private String phoneNumber;
#XmlTransient
#OneToMany(targetEntity = Departments.class, mappedBy = "managerId")
private List<Departments> departmentsCollection;
#Column(name = "EMAIL", table = "EMPLOYEES", nullable = false, length = 25)
#Basic
private String email;
public Employees() {
}
public String getLastName() {
return this.lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Date getHireDate() {
return this.hireDate;
}
public void setHireDate(Date hireDate) {
this.hireDate = hireDate;
}
public Departments getDepartmentId() {
return this.departmentId;
}
public void setDepartmentId(Departments departmentId) {
this.departmentId = departmentId;
}
public Integer getEmployeeId() {
return this.employeeId;
}
public void setEmployeeId(Integer employeeId) {
this.employeeId = employeeId;
}
public Employees getManagerId() {
return this.managerId;
}
public void setManagerId(Employees managerId) {
this.managerId = managerId;
}
public BigDecimal getSalary() {
return this.salary;
}
public void setSalary(BigDecimal salary) {
this.salary = salary;
}
public BigDecimal getCommissionPct() {
return this.commissionPct;
}
public void setCommissionPct(BigDecimal commissionPct) {
this.commissionPct = commissionPct;
}
#XmlTransient
public List<Employees> getEmployeesCollection() {
return this.employeesCollection;
}
public void setEmployeesCollection(List<Employees> employeesCollection) {
this.employeesCollection = employeesCollection;
}
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public Jobs getJobId() {
return this.jobId;
}
public void setJobId(Jobs jobId) {
this.jobId = jobId;
}
public String getPhoneNumber() {
return this.phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
#XmlTransient
public List<Departments> getDepartmentsCollection() {
return this.departmentsCollection;
}
public void setDepartmentsCollection(List<Departments> departmentsCollection) {
this.departmentsCollection = departmentsCollection;
}
public String getEmail() {
return this.email;
}
public void setEmail(String email) {
this.email = email;
}
}
pom.xml that used to create "myjpaclasses-1.jar"...
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>aaa.bbb.ccc.jar</groupId>
<artifactId>myjpaclasses</artifactId>
<version>1</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.test.skip>false</maven.test.skip>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<timestamp>${maven.build.timestamp}</timestamp>
<maven.build.timestamp.format>yyyyMMdd.HHmmss</maven.build.timestamp.format>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.6.3</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc7</artifactId>
<version>12.1.0.2</version>
</dependency>
</dependencies>
<build>
<!-- use for snapshot versioning <finalName>${project.artifactId}-${project.version}-${maven.build.timestamp}</finalName> -->
<finalName>${project.artifactId}-${project.version}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<excludes>
<exclude>junit:junit</exclude>
</excludes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<name>myjpaclasses</name>
</project>
It is likely that EntityManager / JPA library requires that persistence library class loader be the thread context class loader. With jjs -cp option, a fresh class loader is created and that is not the thread context class loader. With "java -cp", the application class loader is set as the thread context loader during initialization.
You may want to try the following in your .js script:
var EntityManagerFactory = Java.type('javax.persistence.EntityManagerFactory');
// set the thread context class to be the loader of EntityManagerFactory class
var cls = EntityManagerFactory.class;
java.lang.Thread.currentThread().contextClassLoader = cls.classLoader;
//... rest of your script..
Please let me know if this works.

Resources