Spring Data JDBC - #Column annotation does't work on setter - java

I need to convert entity's field on fetch and according to this official example I've tried to do that with custom setter:
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;
#Table("entity")
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder(builderClassName = "EntityBuilder")
public class Entity {
#Id
private String someId;
#Transient
private String entityName;
#Column("entity_name")
public String getEntityNameUnmodified() {
return this.entityName;
}
#Column("entity_name")
public void setEntityNameUnmodified(String em) {
this.entityName = em + " Some modification";
}
}
But this completely doesn't work and as a result I get Entity with entityName == null.
I've downloaded GitHub example and run it locally and everything worked. What's wrong with my code?

My code didn't work because of missing #AccessType(AccessType.Type.PROPERTY) annotation.
Working solution:
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.AccessType;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;
#Table("entity")
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder(builderClassName = "EntityBuilder")
#AccessType(AccessType.Type.PROPERTY) // IMPORTANT !!!
public class Entity {
#Id
private String someId;
#Transient
private String entityName;
#Column("entity_name")
public String getEntityNameUnmodified() {
return this.entityName;
}
#Column("entity_name")
public void setEntityNameUnmodified(String em) {
this.entityName = em + " Some modification";
}
}
The thing is that Spring Data JDBC uses fields as accessors for entity's columns by default and without that annotation setters and getters are ignored. Enabling PROPERTY access type solves the problem.

Related

How to mock (simulate) MyBatis-Plus data access layer for unit testing for Model<> Active Record?

In codebase where model classes use Model from https://github.com/baomidou/mybatis-plus
public class SomeDomainClass extends Model<SomeDomainClass> {
How to mock (simulate) MyBatis-Plus data access layer for unit testing?
Longer code example
package com.company.project.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import lombok.experimental.Tolerate;
import java.io.Serializable;
import java.math.BigDecimal;
#Data
#EqualsAndHashCode(callSuper = false)
#Accessors(chain = true)
#TableName("t_order")
#Builder
public class Order extends Model<Order> {
private static final long serialVersionUID = 1L;
#ApiModelProperty(name = "id" , value = "id")
#TableId(value = "f_id", type = IdType.AUTO)
private Long id;
#ApiModelProperty(name = "orderId" , value = "orderId")
private Long orderId;

#Valid not working on nested objects (Java / Spring Boot)

I've been trying for days to find a similar problem online and can't seem to find anything so I am asking my question here.
I have a controller:
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#Validated
#RestController
#RequestMapping("/data")
public class TheController {
private final TheService theService;
#Autowired
public TheController(TheService theService) {
this.theService = theService;
}
#PostMapping(path = "/data", consumes = {MediaType.APPLICATION_JSON_VALUE}, produces = {MediaType.TEXT_PLAIN_VALUE})
public ResponseEntity<String> saveData(#Valid #RequestBody Data data) {
subscriptionDataFeedService.sendData(data.getDataList());
return ResponseEntity.ok()
.body("Data successful.");
}
}
I have the request body class:
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class Data {
#NotEmpty(message = "Data list cannot be empty.")
#JsonProperty(value = "dataArray")
List<#Valid DataOne> dataList;
}
I have the DataOne class:
import com.fasterxml.jackson.annotation.JsonProperty;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class DataOne {
private #NotBlank String currency;
private #NotBlank String accountNumber;
private #NotBlank String finCode;
private String poNumber;
private #NotBlank String invoiceNumber;
private #NotNull Address billTo;
private #NotNull Address soldTo;
private #NotNull LocalDate invoiceDate;
private #NotBlank String billingPeriod;
private #NotNull LocalDate paymentDueDate;
private #NotNull BigDecimal amountDue;
#JsonProperty(value = "activitySummary")
private #NotNull List<#Valid ProductSummary> productSummaryList;
#JsonProperty(value = "accountSummary")
private #NotNull List<#Valid AccountSummary> accountSummaryList;
#JsonProperty(value = "transactions")
private #NotNull List<#Valid Transaction> transactionList;
private #NotNull PaymentByACH paymentByACH;
private #NotNull Address paymentByCheck;
private #NotNull CustomerServiceContact customerServiceContact;
}
And I will include the Address class:
import javax.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class Address {
private #NotBlank String name;
private #NotBlank String address1;
private String address2;
private #NotBlank String city;
private #NotBlank String state;
private #NotBlank String postalCode;
}
I omitted some of the other classes because they aren't needed for my question.
So the problem I am having is that the #Valid annotation is able to validate everything except for the nested classes inside DataOne that aren't a list. In other words, it cannot validate the fields inside Address, PaymentByACH, etc. However, it is able to validate that those objects are #NotNull but is unable to validate the fields inside those classes.
The #Valid is unable to validate the name, address 1, city, etc fields inside of Address. Whenever I add an #Valid tag in front of the Address field inside DataOne I get an HV000028: Unexpected exception during isValid call exception.
How can I validate the nested fields inside of the Address object or any of the nested objects?
TL;DR: The objects that are a list, such as List<#Valid Transaction> transactionList; does validate the fields inside of Transaction but the code does not validate the fields inside of Address.
Great question.
I think you're slightly misusing the #Valid annotation.
How can I validate the nested fields inside of the Address object or
any of the nested objects?
#Valid shouldn't be prefixed to fields you want to validate. That tool is used specifically for validating arguments in #Controller endpoint methods (and sometimes #Service methods). According to docs.spring.io:
"Spring MVC has the ability to automatically validate #Controller
inputs."
It offers the following example,
#Controller
public class MyController {
#RequestMapping("/foo", method=RequestMethod.POST)
public void processFoo(#Valid Foo foo) { /* ... */ }
}
The only reason you should use #Valid anywhere besides in the parameters of a controller (or service) method is to annotate complex types, like lists of objects (ie: DataOne: productSummaryList, accountSummaryList, transactionList). These docs have details for implementing your own validation policy if you'd like.
For your practical needs, you should probably only be using #Valid on controller level methods and the complex types for models referenced by that method. Then use field-level constraints to ensure you don't get things like negative age. For example:
#Data
...
public class Person {
...
#Positive
#Max(value = 117)
private int age;
...
}
Check out this list of constraints you can use from the spring docs. You're already using the #NotNull constraint, so this shouldn't be too foreign. You can validate emails, credit cards, dates, decimals, ranges, negative or positive values, and many other constraints.

Spring boot JPA no returning existing result using findById

I have created a pretty small and simple Spring Boot app using the Oracle database and some JPA queries.
This is the code snippet which is not returning data, which is actually exists in database.
letterRecipientNonOas = letterRecipientNonOasRepository
.findById(Long.valueOf(letterRecipientDTO.getNonOas().getId()))
.orElseThrow(() -> new EntityNotFoundException(LetterRecipientNonOas.class,
Constant.MESSAGE_ENTITY_NOT_FOUND));
here findById is returning empty result set.
this is my repository
package com.care.document.repository;
import java.util.List;
import java.util.Optional;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;
import com.care.document.model.LetterRecipientNonOas;
/**
* The Interface LetterRecipientNonOasRepository.
*/
#Repository
public interface LetterRecipientNonOasRepository extends PagingAndSortingRepository<LetterRecipientNonOas, Long> {
Optional<LetterRecipientNonOas> findByLetterId(Long id);
Optional<LetterRecipientNonOas> findByTitleIgnoreCase(String title);
List<LetterRecipientNonOas> findByTitleContainingIgnoreCase(String title);
List<LetterRecipientNonOas> findAllByTitleIgnoreCaseAndIdNot(String title, Long recipientId);
List<LetterRecipientNonOas> findAllByIdAndLetterId(long id, long letterId);
}
and this is my model class:
package com.care.document.model;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.PrePersist;
import javax.persistence.Table;
import org.springframework.lang.Nullable;
import com.care.admin.model.BaseEntity;
import com.care.admin.util.CommonUtil;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.FieldDefaults;
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#FieldDefaults(level = AccessLevel.PRIVATE)
#Entity
#Table(name = "letter_recipient_non_oas")
public class LetterRecipientNonOas extends BaseEntity implements Serializable {
#Id
Long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "letter_id")
Letter letter;
Integer recipientType; // Action/Info
//byte recipientSubType; // Internal/External/NonOAS
byte recipientCategory; //Internal/External
int orderNo;
String title;
#Nullable
String remarks;
String address;
#PrePersist
private void prePersist() {
this.id = CommonUtil.generateID(this.atRegion);
}
}
I tested, tried different ways but of no use.
There are a couple of scenarios how one might get this impression:
You are looking at the wrong database.
The data isn't there yet when you try to load it, but is when you check.
JPAs caches are known to create such scenarios rather efficiently.
The data looks a little different than you think. This could be caused by invisible or easy to miss content like spaces or even control characters.
You check the database within the transaction that created the data or with a session that allows dirty reads and the insert that created the data wasn't committed yet.

Junit Testing #getmappings with h2 in memory database

I'm having issues unit testing the DbRequest controller. I have one unit test working, but I'm unable to achieve a unit test for the DBRequest controller GET mappings which does a database lookup using hibernate. I' have an H2 in memory database created for the junit tests.
I've tried a variety of different setups, and nothing seems to work correctly.
Edited the below, I'm getting a NullPointer,
java.lang.NullPointerException
at com.lmig.informaticaservice.api.DBcontroltest.saveTest(DBcontroltest.java:74)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
Here is the edited test.
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class DBcontroltest {
#Autowired
DbRequest dbRequest;
#Autowired
ConnectionRequestRepository connectionRequestRepository;
private MockMvc mockMvc;
// #Autowired
//private TestEntityManager entityManager;
#Test
public void saveTest() throws Exception {
ConnectionRequest connectionRequest = new ConnectionRequest((long) 1, "test");
connectionRequestRepository.save(connectionRequest);
System.out.println(connectionRequestRepository.findAll().toString());
mockMvc.perform(get("/api/selectDB/{connectionId}" ,1))
.andExpect(status().isOk());
}
}
Typical JPA repository
package com.test.models;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ConnectionRequestRepository extends JpaRepository<ConnectionRequest, Long> {
}
Here is my controller.
package com.test.api;
import com.models.ConnectionRequest;
import com.test.models.ConnectionRequestRepository;
import java.util.List;
import javax.validation.Valid;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#Data
#RestController
#RequestMapping("/api/")
public class DbRequest {
#Autowired
private ConnectionRequestRepository connectionRequestRepository;
private ConnectionRequest connectionRequest;
#GetMapping("/selectDB")
public List<ConnectionRequest> getAllRequests() {
return connectionRequestRepository.findAll();
}
#GetMapping("/selectDB/{connectionId}")
public ResponseEntity<ConnectionRequest> getRequestById(#PathVariable("connectionId") Long connectionId) throws Exception {
ConnectionRequest connectionRequest = connectionRequestRepository.findById(connectionId)
.orElseThrow(() -> new Exception("Connection Request " + connectionId + " not found"));
return ResponseEntity.ok().body(connectionRequest);
}
}
Here is the model for the database.
package com.testing.models;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
#Data
#Entity
#Table(name = "connrequest", schema = "testschema")
#AllArgsConstructor
#NoArgsConstructor
#EntityListeners(AuditingEntityListener.class)
public class ConnectionRequest {
#Id
#Column(name = "connection_id", nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long connectionId;
#Column(name = "requestor", nullable = false)
private String requestor;
}
It looks like the on of the annotations on the PK of ConnectionRequest is the problem.
The annotation #GeneratedValue tells JPA that it needs to determine the value, so any provided value for the ID will be actively discarded. From the docs
Indicates that the persistence provider must assign primary keys for the entity using a database identity column.
To fix this try either removing that annotation, so then you must always provide an ID, or alternatively, after saving the entity in your test, get the ID that is assigned and call connectionRequestRepository.getOne() with that ID.

Could not obtain identifier from

I am getting following problem when trying to save an Entity into MongoDB database.
I am using Spring CrudRepository
An my code looks as follow:
UserDocument user = processUser();
userRepository.save(user);
This is the error I am getting:
java.lang.IllegalStateException: Could not obtain identifier from UserDocument(id=null, ownerId=..., ...)!
at o.s.d.m.TargetAwareIdentifierAccessor.getRequiredIdentifier(TargetAwareIdentifierAccessor.java:47)
at o.s.d.m.c.EntityOperations$MappedEntity.getId(EntityOperations.java:466)
... 53 frames excluded
UserDocument class:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import lombok.experimental.SuperBuilder;
import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.CompoundIndex;
import org.springframework.data.mongodb.core.index.CompoundIndexes;
import org.springframework.data.mongodb.core.mapping.Document;
#Data
#EqualsAndHashCode(callSuper = true)
#NoArgsConstructor
#AllArgsConstructor
#Accessors(chain = true)
#SuperBuilder
#Document(collection = UserDocument.COLLECTION)
public class UserDocument extends BaseDocument<ObjectId> {
public static final String COLLECTION = "users";
#Id
private ObjectId id;
.....
}
For anyone that is struggling with this problem - in my case it was problem with mapstruct Mapper that as a side effect was populating fields in super class:
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.annotation.Version;
#Data
#NoArgsConstructor(access = AccessLevel.PROTECTED)
#AllArgsConstructor(access = AccessLevel.PROTECTED)
#Accessors(chain = true)
#SuperBuilder
public abstract class BaseDocument<ID extends Serializable> implements Persistable<ID> {
#Version
private Long revision;
#CreatedDate
private Instant createdDateTime;
#LastModifiedDate
private Instant lastModifiedDateTime;
#Override
public boolean isNew() {
return isNull(createdDateTime);
}
}
So make sure these fields are null when you are saving a new entity!
I struggled with this too. In my case, the problem was the #Version field in the object I was trying to save was set to 0. After I set it to null I didn't have this anymore.
I noticed the methods in the trace getQueryForVersion, doSaveVersioned...
java.lang.IllegalStateException: Could not obtain identifier from .....
at org.springframework.data.mapping.TargetAwareIdentifierAccessor.getRequiredIdentifier(TargetAwareIdentifierAccessor.java:48)
at org.springframework.data.mongodb.core.EntityOperations$MappedEntity.getId(EntityOperations.java:527)
at org.springframework.data.mongodb.core.EntityOperations$MappedEntity.getQueryForVersion(EntityOperations.java:556)
at org.springframework.data.mongodb.core.MongoTemplate.doSaveVersioned(MongoTemplate.java:1383)
at org.springframework.data.mongodb.core.MongoTemplate.save(MongoTemplate.java:1370)
at org.springframework.data.mongodb.repository.support.SimpleMongoRepository.save(SimpleMongoRepository.java:88)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)

Categories