I have these objects:
Class of domain
public class Partecipation implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
#OneToMany(mappedBy = "partecipation")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Set<DesignatedCompany> designatedCompanies = new HashSet<>();
...
}
My DTO
public class PartecipationDTO implements Serializable {
private Long id;
...
private Set<DesignatedCompanyDTO> designatedCompanies = new HashSet<>();
...
}
Connected DTO
public class DesignatedCompanyDTO implements Serializable {
private Long id;
...
private PartecipationDTO partecipation;
...
}
And I have this mapper:
public interface PartecipationMapper extends EntityMapper<PartecipationDTO, Partecipation> {
...
PartecipationDTO toDto(Partecipation partecipation);
}
Correctly the code goes in error, because it enters a cyclical condition and in the details here:
public class PartecipationMapperImpl implements PartecipationMapper {
...
protected DesignatedCompanyDTO designatedCompanyToDesignatedCompanyDTO(DesignatedCompany designatedCompany) {
if ( designatedCompany == null ) {
return null;
}
DesignatedCompanyDTO designatedCompanyDTO = new DesignatedCompanyDTO();
designatedCompanyDTO.setId( designatedCompany.getId() );
designatedCompanyDTO.setCompanyEopooCode( designatedCompany.getCompanyEopooCode() );
designatedCompanyDTO.setNote( designatedCompany.getNote() );
designatedCompanyDTO.setPartecipation( toDto( designatedCompany.getPartecipation() ) ); // <--- this line cause the error
return designatedCompanyDTO;
}
...
}
Is it possible to set in the mapper an exclusion for the property of child object in lists? For example, like this:
#Mapping(target = "designatedCompanies[].partecipation", ignore = true)
i have solved the problem, tks to filiphr (https://github.com/mapstruct/mapstruct/issues/933#issuecomment-265952166)
working in the Mappers of the 2 DTO, here the code:
#Mapper(componentModel = "spring", uses = {DesignatedCompanyMapper.class})
public interface PartecipationMapper extends EntityMapper<PartecipationDTO, Partecipation> {
...
#Mapping(target = "designatedCompanies", qualifiedByName="NoPartecipation")
PartecipationDTO toDto(Partecipation partecipation);
...
}
And the Mapper of Iterable Object
public interface DesignatedCompanyMapper extends EntityMapper<DesignatedCompanyDTO, DesignatedCompany> {
#Mapping(source = "partecipation.id", target = "partecipationId")
DesignatedCompanyDTO toDto(DesignatedCompany designatedCompany);
...
#Named("NoPartecipation")
#Mapping(source = "partecipation.id", target = "partecipationId")
#Mapping(target = "partecipation", ignore = true)
DesignatedCompanyDTO toDtoNoPartecipation(DesignatedCompany designatedCompany);
}
Related
In my project, I used a complicated enum for my entity, and I want to do some search function with specification using JPA.
(Entity)
CoDocument.java
#Getter #Setter
#Entity
#Table(name ="co_document")
public class CoDocument {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Id", nullable = false)
private Long id;
#Column(insertable = false, updatable = false , name = "co_application_type")
private CoApplicationType coApplicationType; //DB column is CHAR(255)
}
(Specification)
RepositorySpecification.java
public class RepositorySpecification implements Specification<CoDocument> {
#Serial
private static final long serialVersionUID = -7694054498602732930L;
private final List<SearchCriteria> list;
public CoapRepositorySpecification(List<SearchCriteria> list) {
this.list = list != null ? list : new ArrayList<>();
}
#Override
public Predicate toPredicate(Root<CoDocument> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
List<Predicate> andPredicates = new ArrayList<>();
for (SearchCriteria criteria : list) {
if (criteria.getValue() == null || !StringUtils.hasLength(criteria.getValue().toString())) {
continue;
}
Path<String> rootKey = root.get(criteria.getKey();
andPredicates.add(builder.like(
rootKey,
"%" + criteria.getValue().toString().toLowerCase() + "%"));
}
return builder.and(andPredicates.toArray(new Predicate[0]));
}
(Enum)
CoApplicationType.java
#Getter #Setter
public enum CoApplicationType {
CO_TYPE_A("CO(Maldives)","CO_MALDIVES"),
CO_TYPE_B("CO(ASE)","CO_ASE");
private String applicationType;
private String formName;
private CoApplicationType(String appicationType, String formName){
this.applicationType = appicationType;
this.formName = formName;
}
(Enum converter)
CoApplicationTypeConverter.java
#Converter(autoApply = true)
public class CoApplicationTypeConverter implements AttributeConverter<CoApplicationType, String> {
#Override
public String convertToDatabaseColumn(CoApplicationType coApplicationType) {
if (coApplicationType == null) {
return null;
}
return coApplicationType.getAppicationType();
}
#Override
public CoApplicationType convertToEntityAttribute(String applicationType) {
if (applicationType == null) {
return null;
}
return Stream.of(CoApplicationType.values())
.filter(c -> c.getAppicationType().equals(applicationType))
.findFirst()
.orElseThrow(IllegalArgumentException::new);
}
}
But here comes the exception:
Parameter value [%ASE%] did not match expected type [com.scm.co.constant.CoApplicationType (n/a)]
There's something wrong with the type in the entity, and I have no clue searching the document online.
What I want to do in SQL statement is like:
SELECT * FROM co_document WHERE coApplicationType like "%ASE%";
and it does work in MySQL Workbench.
But I'm not sure how to convert it into JPA with specification, and with complicated enum structure.
Any reply would be appreciated!
You have to add
#Enumerated(EnumType.STRING) // dataType of your enum
#Column(insertable = false, updatable = false , name = "co_application_type")
private CoApplicationType coApplicationType; //DB column is CHAR(255);
for enums you have to add #Enumerated annotation before your column.
I wish to have subquery, which provides me filtering actors by name.
I have a rest controller's method, which returns list of actors as JSON from movie base on movieId. I try to add filters as specification, but I have no idea how to write proper query. Base on "Spring Data JPA Specification for a ManyToMany Unidirectional Relationship" I found solution for subquery, which returns me all actors to proper movie base on movieId. Now I try to write this query.
Actor entity
#Data
#NoArgsConstructor
#Entity
#Table(name = "actors")
public class Actor implements Serializable {
private static final long serialVersionUID = 6460140826650392604L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "actor_id")
private Long actorId;
#Column(name = "first_name")
private String firstName;
#ManyToMany(mappedBy = "actors")
#ToString.Exclude
private List<Movie> movie = new ArrayList<>();
#JsonIgnore
public List<Movie> getMovie() {
return this.movie;
}
}
Movie entity
#Data
#Entity
#NoArgsConstructor
#Table(name = "movies")
public class Movie implements Serializable {
private static final long serialVersionUID = 3683778473783051508L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "movie_id")
private Long movieId;
private String title;
#ManyToMany(cascade = { CascadeType.ALL })
#JoinTable(name = "movies_actors"
, joinColumns = { #JoinColumn(name = "movie_id") }
, inverseJoinColumns = { #JoinColumn(name = "actor_id") })
private List<Actor> actors = new ArrayList<>();
#JsonIgnore
public List<Actor> getActors() {
return this.actors;
}
}
//Rest Controller
#CrossOrigin(origins = "http://localhost:3000")
#RestController
#RequestScope
#RequestMapping("/rest")
public class ActorRestController {
private ActorService actorService;
private MovieService movieService;
#Autowired
public ActorRestController(ActorService actorService, MovieService movieService) {
this.actorService = actorService;
this.movieService = movieService;
}
.
.
.
#GetMapping("movies/{movieId}/actors")
public ResponseEntity<Page<Actor>> getAllActorsFromMovieByIdMovie(#PathVariable(name = "movieId") Long movieId, Pageable pageable) {
Optional<Movie> movieFromDataBase = movieService.findMovieById(movieId);
if (movieFromDataBase.isPresent()) {
return new ResponseEntity<>(actorService.findAllActors(ActorSpec.query(movieId), pageable), HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
.
.
}
// Specification for actor
#NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ActorSpec {
public static Specification<Actor> query(final Long movieId) {
return (root, query, cb) -> {
query.distinct(true);
Subquery<Movie> movieSubQuery = query.subquery(Movie.class);
Root<Movie> movie = movieSubQuery.from(Movie.class);
Expression<List<Actor>> actors = movie.get("actors");
movieSubQuery.select(movie);
movieSubQuery.where(cb.equal(movie.get("movieId"), movieId), cb.isMember(root, actors));
return cb.exists(movieSubQuery);
};
}
}
I would like, my code will return filtered actors by name ex.:
http://localhost:8080/rest/movies/48/actors?name=Collin
will return me
{ "actorId": 159,
"firstName": "Collin",
"lastName": "Konopelski",
"age": 21
},
but in case I do not sent any request param (http://localhost:8080/rest/movies/48/actors), let program return me all actors. I don't want to create new endpoint only for #Requestparam cause, this one is used by UI created in React.
Thanks!
Ok I found,
My solution:
RestController
#GetMapping("movies/{movieId}/actors")
public ResponseEntity<Page<Actor>> getAllActorsFromMovieByIdMovie(#PathVariable(name = "movieId") Long movieId,
#RequestParam(name = "name", required = false) String name,
Pageable pageable) {
Optional<Movie> movieFromDataBase = movieService.findMovieById(movieId);
if (movieFromDataBase.isPresent()) {
return new ResponseEntity<>(actorService.findAllActors(ActorSpec.query(movieId ,name), pageable), HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
Specification
#NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ActorSpec {
public static Specification<Actor> query(final Long movieId, String name) {
return (root, query, cb) -> {
Predicate predicateMovieID = getPredicateByMovieId(movieId, root, query, cb);
if (Strings.isNotBlank(name)) {
Predicate a = cb.and(predicateMovieID, cb.equal(root.get("firstName"), name));
Predicate b = cb.and(predicateMovieID, cb.equal(root.get("lastName"), name));
return cb.or(a,b);
}
return cb.and(predicateMovieID);
};
}
private static Predicate getPredicateByMovieId(Long movieId, Root<Actor> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
query.distinct(true);
Subquery<Movie> movieSubQuery = query.subquery(Movie.class);
Root<Movie> movie = movieSubQuery.from(Movie.class);
Expression<List<Actor>> actors = movie.get("actors");
movieSubQuery.select(movie);
movieSubQuery.where(cb.equal(movie.get("movieId"), movieId), cb.isMember(root, actors));
return cb.exists(movieSubQuery);
}
}
I have about 300 JPA entities where the getters are annotated with persistence annotations. I would like to find a way to move all such annotations to the properties instead and remove all getters and setters. I did this manually for about 100 of these classes but it's very time consuming and mind numbing work.
I'm looking at source code transformation tools like Spoon but still not sure it can do what I need it to do.
More specifically, how can I transform this code:
#Entity
#Table(name = "crm_ticket")
public class CrmTicket implements Serializable {
private static final long serialVersionUID = -902718555957517699L;
private CrmAccount crmAccount;
private ItsType subType;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "account")
public CrmAccount getCrmAccount() {
return crmAccount;
}
public void setCrmAccount(CrmAccount crmAccount) {
this.crmAccount = crmAccount;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "sub_type")
public ItsType getSubType() {
return subType;
}
public void setSubType(ItsType type) {
this.subType = type;
}
}
To this:
#Entity
#Table(name = "crm_ticket")
#Data
public class CrmTicket implements Serializable {
private static final long serialVersionUID = -902718555957517699L;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "account")
private CrmAccount crmAccount;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "sub_type")
private ItsType subType;
}
Spoon would work well for this, you would use aField.addAnnotation and aSetter.delete.
I ended up using Spoon. It wasn't that painful. I configured their maven plugin to run my processor and it transformed the code of my Entity classes. I then copied the generated code back to my project and removed the plugin configuration.
Here's my processor code:
public class JpaAnnotationMover extends AbstractProcessor<CtMethod> {
Pattern p1 = Pattern.compile("return.*this\\.(.*?)$");
Pattern p2 = Pattern.compile("return(.*?)$");
#Override
public boolean isToBeProcessed(CtMethod method) {
return isInEntity(method) && isAGetter(method) && hasOneStatement(method) && !isTransient(method);
}
#Override
public void process(CtMethod ctMethod) {
CtType parentClass = ctMethod.getParent(CtType.class);
String fieldName = getFieldName(ctMethod);
if (fieldName == null) {
log.warn(String.format("expected field name for %s not found.", ctMethod.getSimpleName()));
return;
}
CtField field = parentClass.getField(fieldName);
if (field == null) {
log.warn(String.format("Expected field %s not found.", fieldName));
return;
}
for (CtAnnotation<? extends Annotation> annotation : ctMethod.getAnnotations()) {
field.addAnnotation(annotation);
}
parentClass.removeMethod(ctMethod);
log.info(String.format("Processed method %s:%s", parentClass.getSimpleName(), ctMethod.getSimpleName()));
// find corresponding setter
String setterName = "set" + WordUtils.capitalize(fieldName);
#SuppressWarnings("unchecked") CtMethod setter = parentClass
.getMethod(getFactory().Type().createReference("void"), setterName, ctMethod.getType());
if (setter == null) {
log.warn(String.format("Unable to find setter for %s", fieldName));
return;
}
parentClass.removeMethod(setter);
if (!parentClass.hasAnnotation(Data.class)) {
parentClass.addAnnotation(getFactory().createAnnotation(getFactory().Type().createReference(Data.class)));
}
}
private Boolean isInEntity(CtMethod method) {
CtType parentClass = method.getParent(CtType.class);
return parentClass.hasAnnotation(Entity.class);
}
private Boolean isAGetter(CtMethod method) {
return method.getSimpleName().contains("get");
}
private Boolean hasOneStatement(CtMethod method) {
return method.getBody().getStatements().size() == 1;
}
private Boolean isTransient(CtMethod method) {
return method.hasAnnotation(Transient.class);
}
private String getFieldName(CtMethod method) {
String statement = method.getBody().getLastStatement().toString();
Matcher m = p1.matcher(statement);
Matcher m2 = p2.matcher(statement);
return m.matches() ? m.group(1).trim() : m2.matches() ? m2.group(1).trim() : null;
}
}
I have a service wherein I use entities with following structure
#Entity
public class Product {
#Id
private Integer id;
private String productId;
private List<MobileCompany<?>> companies;
// ...
}
public class MobileCompany<T> extends Company<T> {
private static final long serialVersionUID = -4809948191835736752
private String simType;
// ....
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(name= "samsung", value = Samsung.class),
#JsonSubTypes.Type(name= "htc",value = Htc.class)})
public class Company<T> implements Serializable {
private static final long serialVersionUID = -8869676577723436716L;
private T info;
private String type;
// ...
}
My service looks similar to below
Product sample(#RequestBody Product product) {
....// not doing any update on product
return product;
}
with request as below ..
{
id:0903673902783,
companies:[
{
simType:"DUAL",
type:"samsung"
}
]
}
and get back below response
{
id:0903673902783,
companies:[
{
simType:"DUAL",
type:"MobileCompany"
}
]
}
Why am I getting type as "MobileCompany" instead of "samsung" ?
Note : "MobileCompany" is the name of the class which extends Company and in the response I am getting "MobileCompany" as type instead of the type specified in the request. Product is having list of MobileCompany.
I'm getting this errors when trying to create relation between 2 entities, this time i'm doing this in different way - passing JSON with 2 object into helper class and then getting those object and persisting them, one by one and setting the relation. When i remove setters of relation : 1. newPerson.setKoordynator(koordynatorzyPraktykEntity);
2.koordynatorzyPraktykEntity.setKoordynatorByIdOsoby(newPerson);
then it is persisting both entities without a problem, with setters only first one (KoordynatorzyPraktykEntity) is persisted (idKoordynatora = 1, idOsoby =0, test = test )
Here is the important part of error from POSTMAN ( full log http://pastebin.com/SRmnPMBH )
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: praktyki.core.entities.KoordynatorzyPraktykEntity; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: praktyki.core.entities.KoordynatorzyPraktykEntity
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:978)
org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:868)
javax.servlet.http.HttpServlet.service(HttpServlet.java:644)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842)
javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
KoordynatorzyEntity:
#Entity
#Table(name = "koordynatorzy_praktyk", schema = "public", catalog = "praktykidb")
public class KoordynatorzyPraktykEntity {
private int idKoordynatoraPraktyk;
private int idOsoby;
private String doTestow;
private OsobyEntity koordynatorByIdOsoby;
private Collection<KoordynatorzyKierunkowiEntity> koordynatorzyByIdKierunku;
#Id
#GeneratedValue
#Column(name = "id_koordynatora_praktyk")
public int getIdKoordynatoraPraktyk() {
return idKoordynatoraPraktyk;
}
public void setIdKoordynatoraPraktyk(int idKoordynatoraPraktyk) {
this.idKoordynatoraPraktyk = idKoordynatoraPraktyk;
}
#Basic
#Column(name = "id_osoby")
public int getIdOsoby() {
return idOsoby;
}
public void setIdOsoby(int idOsoby) {
this.idOsoby = idOsoby;
}
/*
STUFF
*/
#JsonIgnore
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "id_osoby", referencedColumnName = "id_osoby", insertable = false , updatable = false)
public OsobyEntity getKoordynatorByIdOsoby() {
return koordynatorByIdOsoby;
}
public void setKoordynatorByIdOsoby(OsobyEntity koordynatorByIdOsoby) {
this.koordynatorByIdOsoby = koordynatorByIdOsoby;
}
#JsonIgnore
#OneToMany(mappedBy = "koordynatorzyByIdKierunku", cascade = CascadeType.ALL)
#LazyCollection(LazyCollectionOption.FALSE)
public Collection<KoordynatorzyKierunkowiEntity> getKoordynatorzyByIdKierunku() {
return koordynatorzyByIdKierunku;
}
public void setKoordynatorzyByIdKierunku(Collection<KoordynatorzyKierunkowiEntity> koordynatorzyByIdKierunku) {
this.koordynatorzyByIdKierunku = koordynatorzyByIdKierunku;
}
OsobyEntity:
#Entity
#Table(name = "osoby", schema = "public", catalog = "praktykidb")
public class OsobyEntity {
private int idOsoby;
private String tytulZawodowy;
private String imie;
private String nazwisko;
private String email;
private String telefonKomorkowy;
private String telefonStacjonarny;
private KoordynatorzyPraktykEntity koordynator;
#Id
#GeneratedValue
#Column(name = "id_osoby")
public int getIdOsoby() {
return idOsoby;
}
public void setIdOsoby(int idOsoby) {
this.idOsoby = idOsoby;
}
/*
STUFF
*/
#OneToOne(mappedBy = "koordynatorByIdOsoby", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
public KoordynatorzyPraktykEntity getKoordynator() {
return koordynator;
}
public void setKoordynator(KoordynatorzyPraktykEntity koordynator) {
this.koordynator = koordynator;
}
KoordynatorzyPraktykService :
public class KoordynatorzyPraktykService implements iKoordynatorzyPraktykService {
#Autowired
private iKoordynatorzyPraktykDAO ikoordynatorzyPraktykDAO;
#Autowired
private iOsobyDAO iosobyDAO;
#Override
public KoordynatorzyPraktykEntity addCoordinator(KoordynatorzyPraktykEntity koordynatorzyPraktykEntity) {
return ikoordynatorzyPraktykDAO.addCoordinator(koordynatorzyPraktykEntity);
}
/*
STUFF
*/
#Override
public OsobyEntity addPerson(OsobyEntity osobyEntity, KoordynatorzyPraktykEntity koordynatorzyPraktykEntity) {
OsobyEntity newPerson = iosobyDAO.addPerson(osobyEntity);
newPerson.setKoordynator(koordynatorzyPraktykEntity);
System.out.println(koordynatorzyPraktykEntity.toString()); //shows idKoordynatora: 1 idOsoby: 0 test: test
System.out.println(newPerson.toString()); //shows idOsoby: 32768 imie: Tomasz nazwisko: Potempa
int idOsoby = newPerson.getIdOsoby();
koordynatorzyPraktykEntity.setIdOsoby(idOsoby);
System.out.println(koordynatorzyPraktykEntity.toString()); //shows idKoordynatora: 1 idOsoby: 32768 test: test
koordynatorzyPraktykEntity.setKoordynatorByIdOsoby(newPerson);
return newPerson;
}
Both DAOs have em.persist(entity)
and POST of KoordynatorzyPraktykController:
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<KoordynatorzyPraktykEntity> addCoordinator(#RequestBody Koordynator newCoordinator) {
KoordynatorzyPraktykEntity addCoordinator = ikoordynatorzyPraktykService.addCoordinator(newCoordinator.getKoordynator());
OsobyEntity addPerson = ikoordynatorzyPraktykService.addPerson(newCoordinator.getOsoba(), addCoordinator);
if (addCoordinator !=null && addPerson !=null) {
return new ResponseEntity<KoordynatorzyPraktykEntity>(addCoordinator, HttpStatus.OK);
}
else {
return new ResponseEntity<KoordynatorzyPraktykEntity>(HttpStatus.NOT_FOUND);
}
}
Helper Class Koordynator:
public class Koordynator {
private KoordynatorzyPraktykEntity koordynator;
private OsobyEntity osoba;
public KoordynatorzyPraktykEntity getKoordynator() {
return koordynator;
}
public void setKoordynator(KoordynatorzyPraktykEntity koordynator) {
this.koordynator = koordynator;
}
public OsobyEntity getOsoba() {
return osoba;
}
public void setOsoba(OsobyEntity osoba) {
this.osoba = osoba;
}
}
and this is parsed JSON into controller through POSTMAN
{
"koordynator":
{
"doTestow" : "test"
},
"osoba":
{
"tytulZawodowy" : "inzynier",
"imie" : "Tomasz",
"nazwisko" : "Potempa",
"email" : "tp#tp.pl",
"telefonKomorkowy" : "124675484",
"telefonStacjonarny" : "654786484"
}
}
Only way I got it work
Class A:
#OneToMany(cascade = CascadeType.MERGE)
private List<B> b;
Class B:
#ManyToOne
#JoinColumn(name = "aId", referencedColumnName = "id")
private A a;
private String test;
Service:
A a = new A();
//Create without children
aFacade.create(a);
//items
List<B> list = new ArrayList<>();
B b = new B();
b.setTest("Hello");
b.setA(a);
list.add(b);
//merge
a.setB(list);
aFacade.edit(a);
you hit the exception below simply because the entity isn't in the Entity Manager's session at the moment you are trying to persist it. That's due to laziness of your association.
"detached entity passed to persist: praktyki.core.entities.KoordynatorzyPraktykEntity;"
Try calling em.merge() to attach it to the session.