Avoiding loading LAZY collection in Spring Data Jpa - jpa

I have following relationship:
#Entity class Shop {
#OneToMany(mappedBy = "shop", fetch = LAZY)
private List<Employee> employees = new LinkedList<>();
}
and
#Entity class Employee {
#ManyToOne
private Shop shop;
}
I've declared Spring Data repository like this:
public interface ShopRepository extends JpaRepository<Shop, Long> {}
Calling ShopRepository#findOne(id) method forces fetching of the List<Employee> employees which is LAZY relationship.
I have service which uses Shop repository:
#Service
#Transactional(readOnly = true)
public class ShopService {
private final ShopRepository shopRepository;
#Autowired
public ShopService(ShopRepository shopRepository) {
this.shopRepository = shopRepository;
}
public Shop find(Long id) {
return shopRepository.findOne(id);
}
}
The service method is called within another controller method:
#RequestMapping(value = "api/schedule/{shopId:[0-9]+}/{date:\\d{4}-\\d{2}-\\d{2}}", method = RequestMethod.GET)
#ResponseBody
public Schedule getSchedule(#PathVariable Long shopId,
#PathVariable #DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date) {
Schedule schedule = scheduleService.findSchedule(shopId, date);
if(schedule != null)
return schedule;
else {
Shop shop = shopService.find(shopId);
Schedule empty = new Schedule(shop, date);
return empty;
}
}
How to get rid of fetching employees relationship?

I found solution.
Actually I used #JsonManagedReference/#JsonBackRefernce on my entity to prevent cycling while marshaling to JSON. It causes fetching LAZY loading data.
To avoid this you should add Hibernate4Module to MappingJackson2HttpMessageConverter.
More info at this post: Avoid Jackson serialization on non fetched lazy objects

Related

Using the PostgreSQL JSONB type with Hibernate Reactive

I am migrating my Quarkus project from the classic Hibernate ORM to Hibernate Reactive and I faced a problem with JSONB field mapping.
Here is the entity:
#Entity
#TypeDef(name = JsonTypes.JSON_BIN, typeClass = JsonBinaryType::class)
class MyEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "myEntityIdGenerator")
#SequenceGenerator(name = "myEntityIdGenerator", sequenceName = "my_entity_id_seq", allocationSize = 10)
var id: Long? = null
// Usage of a plain JsonNode instead of a mapped class is intentional,
// as the app receives a request with raw JSON data and should store it without any processing
#Type(type = JsonTypes.JSON_BIN)
#NotNull
lateinit var jsonData: JsonNode
}
The project has the io.quarkiverse.hibernatetypes:quarkus-hibernate-types:0.2.0 dependency to handle JSON types.
This code worked fine with blocking Hibernate API, but when trying to persist a MyEntity using the Hibernate Reactive, I get the following exception:
io.vertx.core.impl.NoStackTraceThrowable: Parameter at position[1] with class = [com.fasterxml.jackson.databind.node.ObjectNode] and value = [{"field1":"some value"}] can not be coerced to the expected class = [java.lang.Object] for encoding.
Is this a bug or custom types should be handled differently while using Hibernate Reactive?
Hibernate Types is not compatible with Hibernate Reactive.
But you have three options to map a Json with Hibenrnate Reactive:
Use io.vertx.core.json.JsonObject
Map it as String and use a converter
Create a UserType
1. JsonObject
Example with io.vertx.core.json.JsonObject:
#Entity
private static class EntityWithJson {
...
private JsonObject jsonObj;
...
}
You can see a working example in the repository: JsonTypeTest
2. Using a converter
Example using a converter:
class EntityWithJson {
#Column(columnDefinition = "json")
#Convert(converter = StringToJson.class)
private String json;
...
}
#Converter
public class StringToJson implements AttributeConverter<String, JsonObject> {
#Override
public JsonObject convertToDatabaseColumn(String string) {
if (string == null) {
return null;
}
return new JsonObject(string);
}
#Override
public String convertToEntityAttribute(JsonObject dbData) {
if (dbData == null) {
return null;
}
return dbData.encodePrettily();
}
}
You can see a working example in the repository: JsonTypeTest
3. UserType
class EntityWithJson {
#Type(type="org.example.Json")
#Column(columnDefinition = "json")
private JsonObject jsonObj;
}
package org.example
public class Json implements UserType {
// ... Implementation left out for brevity
}
You can see a working example in the repository: UserJsonTypeTest

Query for multiple values of the same property with queryDSL and Spring Data JPA

Is there a way to query for multiple values of the same property with Spring DataREST JPA and querydsl? I am not sure what the format of the query URL should be and if I need extra customization in my bindings. I couldn't find anything in documentation. If I have a "student" table in my database with a "major" column with corresponding Student entity I would assume that querying for all students which have "math" and "science" majors would look like http://localhost:8080/students?major=math&major=science. However in this query only the first part is being taken and major=science is ignored
Below example customizes Querydsl web support to perform collection in operation. URI /students?major=sword&major=magic searches for students with major in ["sword", "magic"].
Entity and repository
public class Student {
private Long id;
private String name;
private String major;
}
public interface StudentRepos extends PagingAndSortingRepository<Student, Long>,
QuerydslPredicateExecutor<Student>,
QuerydslBinderCustomizer<QStudent> {
#Override
default void customize(QuerydslBindings bindings, QStudent root) {
bindings.bind(root.major)
.all((path, value) -> Optional.of(path.in(value)));
}
}
Test data
new Student("Arthur", "sword");
new Student("Merlin", "magic");
new Student("Lancelot", "lance");
Controller
#RestController
#RequestMapping("/students")
#RequiredArgsConstructor
public class StudentController {
private final StudentRepos studentRepos;
#GetMapping
ResponseEntity<List<Student>> getAll(Predicate predicate) {
Iterable<Student> students = studentRepos.findAll(predicate);
return ResponseEntity.ok(StreamSupport.stream(students.spliterator(), false)
.collect(Collectors.toList()));
}
}
Test case
#Test
#SneakyThrows
public void queryAll() {
mockMvc.perform(get("/students"))
.andExpect(status().isOk())
.andExpect(jsonPath("$").isArray())
.andExpect(jsonPath("$", hasSize(3)))
.andDo(print());
}
#Test
#SneakyThrows
void querySingleValue() {
mockMvc.perform(get("/students?major=sword"))
.andExpect(status().isOk())
.andExpect(jsonPath("$").isArray())
.andExpect(jsonPath("$", hasSize(1)))
.andExpect(jsonPath("$[0].name").value("Arthur"))
.andExpect(jsonPath("$[0].major").value("sword"))
.andDo(print());
}
#Test
#SneakyThrows
void queryMultiValue() {
mockMvc.perform(get("/students?major=sword&major=magic"))
.andExpect(status().isOk())
.andExpect(jsonPath("$").isArray())
.andExpect(jsonPath("$", hasSize(2)))
.andExpect(jsonPath("$[0].name").value("Arthur"))
.andExpect(jsonPath("$[0].major").value("sword"))
.andExpect(jsonPath("$[1].name").value("Merlin"))
.andExpect(jsonPath("$[1].major").value("magic"))
.andDo(print());
}
The full Spring Boot application is in Github

IllegalArgumentException: NamedQuery using Spring JPA

I am using namedquery for rest api using Spring JPA. The named query is implemented in my entity class:
#Entity
#Table(name="SPECIMEN_TB")
#NamedQueries({
#NamedQuery(name="SpecimenTb.findBySpecimenNo", query="select s from SpecimenTb s where s.specimenNo = :specimenNo"),
})
public class SpecimenTb implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SPECIMEN_TB_ROWID_GENERATOR")
#Column(name="ROW_ID")
private long rowId;
#Column(name="SPECIMEN_NO", unique = true)
private String specimenNo;
My controller looks like this:
#RestController
public class RistoreController {
#Autowired
private RistoreService ristoreService;
#RequestMapping(
value = "/ristore/foundation/{specno}",
method = RequestMethod.GET,
produces = "application/json")
public ResponseEntity<SpecimenTb> getFmSpecimen(#PathVariable("specno") String specno) {
List<SpecimenTb> specimens = ristoreService.findBySpecimenNo(specno);
if (specimens == null) {
return new ResponseEntity<SpecimenTb>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<SpecimenTb>(specimens.get(0), HttpStatus.OK);
}
I have a service bean which calls JPA repository findBySpecimenNo method.
#Service
public class RistoreServiceBean implements RistoreService {
#Autowired
private SpecimenRepository specimenRepository;
#Override
public List<SpecimenTb> findAll() {
List<SpecimenTb> specimens = specimenRepository.findAll();
return specimens;
}
#Override
public List<SpecimenTb> findBySpecimenNo(String specimenNo) {
List<SpecimenTb> specimens = specimenRepository.findBySpecimenNo(specimenNo);
return specimens;
}
When I start the Spring Boot Application and type in the url "http://localhost:8080/ristore/foundation/SKM1", I got the following error:
java.lang.IllegalArgumentException: Parameter with that position [1] did not exist
What did I do wrong?
Looks like you can't use a named parameter with the #NamedQuery based on the docs I read. Have you tried with ?1 instead?
Reason that named parameter doesn't work is that you also have to add the annotation on the method parameter so Spring knows which parameter matches to what placeholder in the query.

How to reference an entity with inheritance in Spring Data REST when POSTing new entity?

I have entities with joined inheritance:
Supporter
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "supporterType")
#JsonSubTypes({
#JsonSubTypes.Type(value = PersonSupporterEntity.class, name = "PERSON"),
#JsonSubTypes.Type(value = CompanySupporterEntity.class, name = "COMPANY")
})
#DiscriminatorColumn(name="supporter_type")
#Table(name = "supporter")
public class SupporterEntity extends UpdatableEntity {
private long id;
private SupporterType supporterType;
private PartnerEntity partner;
...
}
PersonSupporter
#Entity
#DiscriminatorValue("PERSON")
#Table(name = "person_supporter")
public class PersonSupporterEntity extends SupporterEntity {
...
}
CompanySupporter
#Entity
#DiscriminatorValue("COMPANY")
#Table(name = "company_supporter")
public class CompanySupporterEntity extends SupporterEntity {
...
}
I have another entity which references SupporterEntity
#Entity
#Table(name = "contact")
public class ContactEntity extends UpdatableEntity {
private long id;
private SupporterEntity supporter;
...
#ManyToOne // same error with #OneToOne
#JoinColumn(name = "supporter_id", referencedColumnName = "id", nullable = false)
public SupporterEntity getSupporter() {
return supporter;
}
...
}
Repositories
#Transactional
#RepositoryRestResource(collectionResourceRel = "supporters", path = "supporters")
public interface SupporterEntityRepository extends JpaRepository<SupporterEntity, Long> {
#Transactional(readOnly = true)
#RestResource(path = "by-partner", rel = "by-partner")
public Page<SupporterEntity> findByPartnerName(#Param("name") String name, Pageable pageable);
}
#Transactional
#RepositoryRestResource(collectionResourceRel = "person_supporters", path = "person_supporters")
public interface PersonSupporterEntityRepository extends JpaRepository<PersonSupporterEntity, Long> {
}
#Transactional
#RepositoryRestResource(collectionResourceRel = "company_supporters", path = "company_supporters")
public interface CompanySupporterEntityRepository extends JpaRepository<CompanySupporterEntity, Long> {
}
#Transactional
#RepositoryRestResource(collectionResourceRel = "contacts", path = "contacts")
public interface ContactEntityRepository extends JpaRepository<ContactEntity, Long> {
#Transactional(readOnly = true)
#RestResource(path = "by-supporter", rel = "by-supporter")
public ContactEntity findBySupporterId(#Param("id") Long id);
}
I use Spring Boot, Spring Data REST, Spring Data JPA, Hibernate, Jackson. When I try to create a new ContactEntity with a post request like this:
{
"supporter":"/supporters/52",
"postcode":"1111",
"city":"Test City 1",
"address":"Test Address 1",
"email":"test1#email.com",
"newsletter":true
}
I get this exception:
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (VALUE_STRING), expected FIELD_NAME: missing property 'supporterType' that is to contain type id (for class com.facer.domain.supporter.SupporterEntity)
at [Source: HttpInputOverHTTP#4321c221; line: 1, column: 2] (through reference chain: com.facer.domain.supporter.ContactEntity["supporter"])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148) ~[jackson-databind-2.4.4.jar:2.4.4]
After 2 days of debugging I found a way, but I kinda guessed it. So if I post it like this:
{
"supporter":{
"supporterType":"PERSON",
"id":"52"
},
"postcode":"1111",
"city":"Test City 1",
"address":"Test Address 1",
"email":"test1#email.com",
"newsletter":true
}
It works, but I don't know why. What's wrong with the other request? It works like that everywhere else when the referenced entity does not have inheritance.
Just another workaround using a RelProvider:
Do not use #JsonTypeInfo
Create a RelProvider for SupporterEntity sub-classes
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class SupporterEntityRelProvider implements RelProvider {
#Override
public String getCollectionResourceRelFor(final Class<?> type) {
return "supporters";
}
#Override
public String getItemResourceRelFor(final Class<?> type) {
return "supporter";
}
#Override
public boolean supports(final Class<?> delimiter) {
return org.apache.commons.lang3.ClassUtils.isAssignable(delimiter, SupporterEntity.class);
}
}
See also:
https://jira.spring.io/browse/DATAREST-344
http://docs.spring.io/spring-hateoas/docs/current/reference/html/#configuration.at-enable
It looks like a Jackson problem. To be specific, it's the following code in com.fasterxml.jackson.databind.deser.SettableBeanProperty:
if (_valueTypeDeserializer != null) {
return _valueDeserializer.deserializeWithType(jp, ctxt, _valueTypeDeserializer);
}
return _valueDeserializer.deserialize(jp, ctxt);
Without inheritance _valueDeserializer.deserialize would be called which in turn runs some Spring code to convert the URI to a Supporter.
With inheritance _valueDeserializer.deserializeWithType is called and vanilla Jackson, of course, expects an object, not a URI.
If supporter was nullable you could first POST to /contacts and then PUT the supporter's URI to /contacts/xx/supporter. Unfortunately I am not aware of any other solution.
You should be able to workaround this by setting #JsonTypeInfo(use= JsonTypeInfo.Id.NONE) at the property/method level e.g.
Try with this:
#ManyToOne // same error with #OneToOne
#JoinColumn(name = "supporter_id", referencedColumnName = "id", nullable = false)
#JsonTypeInfo(use= JsonTypeInfo.Id.NONE)
public SupporterEntity getSupporter() {
return supporter;
}

Transaction using JSF2, EJB3, JPA2

What is the best way to handle transactions in this environment?
I have a Transacao class, which has a collection of Transacao.
public class Transacao {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "pai")
private List<Transacao> filhos;
}
I load this in JSF from a EJB, something like:
public class TransacaoBean {
#EJB
private TransacaoService transacaoService;
private void edit(Long id) {
this.transacao = transacaoService.findById(id);
}
}
although, if I want to get the collection of filhos, I have to do this:
public class TransacaoBean {
...
private void edit(Long id) {
this.transacao = transacaoService.findById(id);
log.info(this.transacao.getFilhos.size()); //this throws a LazyInitializationException
}
}
and I get an Exception.
What is the best way to have this loaded in my JSF? I'm considering creating a Filter and using USerTransaction to keep the transaction open for the request or fetching the filhos in my EJB. Is there a better solution to this, which one is better?
The fetch's default value of the #OneToMany is FetchType.LAZY.
You can set it FetchType.EAGER to use them in non-managed environment.
Or you can make another EJB or method for getting a list or just the size.
public class TransacaoService {
public Transacao findById(final long id) {
...
}
public long getFilhosSize(final long id) {
// SELECT f FROM Transacao AS t WHERE t.pai.id=:id
}
#PersistenceContext
private EntityManager entityManager;
}