Spring Data JPA update a Row without getting the row ById - spring

I want to update the table using spring-jpa
This is my Entity Class
public class RewardEntity {
#Id
#Column(name = "reward_id", columnDefinition = "bigserial")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long rewardId;
#Column(name = "reward_title", nullable = false)
private String rewardTitle;
#Column(name = "reward_text")
private String rewardText;
#Column(name = "reward_type", nullable = false)
private String rewardType;
#Column(name = "reward_for_code", nullable = false)
private String rewardFor;
#Column(name = "reward_from_date", nullable = false)
private OffsetDateTime rewardFromDate;
#Column(name = "reward_to_date", nullable = false)
private OffsetDateTime rewardToDate;
#Column(name = "is_display_on", nullable = false)
private Boolean isDisplayOn;
#Column(name = "created_id", length = 50, nullable = false)
private String createdId;
#Column(name = "updated_id", length = 50)
private String updatedId;
#Column(name = "created_date", columnDefinition = "timestamptz", nullable = false)
private OffsetDateTime createdDate;
#Column(name = "last_modified_date", columnDefinition = "timestamptz")
private OffsetDateTime lastModifiedDate;
}
I have a PutMapping Spring boot API that gets below Json Input
{
"rewardId": 53,
"rewardTitle": "Reward is Allocated",
"rewardText": "Reward allocated for your recent purchase with our shop located at ABC-Mall",
"rewardType": "Informational",
"rewardFor": "Customer",
"rewardFromDate": "2019-04-12T00:00:00+05:30",
"rewardToDate": "2019-04-15T00:00:00+05:30",
"isDisplayOn": false
}
My Controller takes Principal object for both creation and updating the rewards table
#PutMapping
public ResponseEntity<RewardsResponse> updateRewards(Principal updatedPrincipal,
#RequestBody RewardUpdateRequest RewardUpdateRequest) {
But I won't send my createdId or updatedId from my Angular-UI.. So when i try to insert the updated-entity in to the table, using the below service-layer code
public RewardEntity updateReward(Principal principal, rewardEntity rewardEntity) {
String updatedId = null != principal ? principal.getName() : "defaultUpdatedId";
rewardEntity.setUpdatedCdsId(updatedId);
rewardEntity.setLastModifiedDate(OffsetDateTime.now());
return rewardRepository.save(rewardEntity);
}
I get the below error
could not execute statement; SQL [n/a]; constraint [created_id]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
My assumption is that RewardEntity gets updated in the same row by mapping the ID that we pass and update only the fields that i set and do not touch rest of the fields ...
Should i first get my RewardEntity object from the DB based on the ID and then update on top of it ?? This makes the code connect DB twice for every update.
Request your inputs please

I would first get reference object using updatedId
RewardEntity rewardEntity = rewardRepository.getOne(updatedId )
update this object as per your requirement
rewardEntity.setLastModifiedDate(OffsetDateTime.now());
and finally use save to update this.
return rewardRepository.save(rewardEntity);
getOne() returns a reference to the entity and internally invokes EntityManager.getReference() method. It will always return a proxy without hitting the database (lazily fetched).

Related

Spring data rest ManyToMany mapping PUT/update operation is not replacing the nested object

I started to learn spring data rest. I'm doing PUT operation and it's not working for the nested objects for ManyToMany relationship, whereas it works fine for OneToMany relation.
Entities structures:
#Table(name="CONFIG_DTLS",schema = "app_txn")
#Entity
public class Config {
#Id
#GenericGenerator(name = "UUIDGenerator", strategy = "uuid2")
#GeneratedValue(generator = "UUIDGenerator")
#Column(name = "ID", updatable = false, nullable = false)
private UUID id;
#Column(name = "NAME", nullable = false, length = 75)
private String name;
/*Unable to replace the data in the MBR_CONFIG_MAPPING table in the put operation.
When the control comes to #HandleBeforeSave annotated method in PUT operation,
the request data contains the existing Member info instead of the one which i'm passing in the PUT request body */
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE},fetch = FetchType.EAGER)
#JoinTable(schema = "app_txn", name = "MBR_CONFIG_MAPPING",
joinColumns ={#JoinColumn(name="CONFIG_ID",referencedColumnName = "ID")},
inverseJoinColumns = {#JoinColumn(name="MBR_ID",referencedColumnName = "ID")}
)
private Set<Member> members;
//able to replace the notifications completely in PUT operation
#OneToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "CONFIG_ID",referencedColumnName = "ID")
private Set<Notification> notifications;
}
Member.java
#Table(name="MBR_DTLS",schema = "app_txn")
#Entity
public class Member {
#Id
#GenericGenerator(name = "UUIDGenerator", strategy = "uuid2")
#GeneratedValue(generator = "UUIDGenerator")
#Column(name = "ID", updatable = false, nullable = false)
private UUID id;
#Column(name = "OTHER_MBR_DATA", updatable = false)
private String otherMbrData;
}
Notification.java
#Table(name="NOTIFICATIONS",schema = "app_txn")
#Entity
public class Notification {
#Id
#GenericGenerator(name = "UUIDGenerator", strategy = "uuid2")
#GeneratedValue(generator = "UUIDGenerator")
#Column(name = "ID", updatable = false, nullable = false)
private UUID id;
#Column(name="LEVEL")
private String level;
#Column(name="EMAIL")
private String email;
}
Interfaces:
#RepositoryRestResource(collectionResourceRel = "configs", path="configs")
public interface ConfigRepo extends PagingAndSortingRepository<Config,UUID> {
}
#RepositoryRestResource(exported=false) // don't want to users to manipulate it directly.
public interface MemberRepo extends PagingAndSortingRepository<Member,Object> {
}
Here I don't want to add or modify anything in the MBR_DTLS table as it is loaded by another backend process. I want to update only the mapping details MBR_CONFIG_MAPPING table whenever user does the PUT/update operation. POST/create operation is working fine. Please share your thoughts on how to fix this and if you have any questions add it in the comment section.
PS: I referred some links online but that does not help much - Spring Data REST - PUT request does not work properly since v.2.5.7

How to map a nested JSON Object as an SQL table row in Spring Boot

I'm using Spring to develop APIs along with JPA. I'm handling a POST request that accepts #RequestBody as a JSON object that looks like this-
{
"id": "323",
"name": "Sam",
"gpsLocation": {
"latitude": 66.7492558,
"longitude": 97.133258
}
}
And an SQL User Table that has the following columns-
id | name | latitude | longitude
Is there a way in Spring to map this nested json object directly to these table columns?
This is what my User.java and GpsLocation.java entity classes look like right now-
#Table(name = "user")
#Entity
public class UnderObservation {
#Column(name = "name", nullable = false)
private String name;
#Id
#Column(name = "id", nullable = false)
private String userID;
private GpsLocation location;
}
#Entity
public class GpsLocation {
#Column(name = "Latitude", nullable = false)
private Double Latitude;
#Column(name = "Longitude", nullable = false)
private Double Longitude;
}
I'm looking for a way to "flatten/unwrap" GpsLocation class so that it directly fits into the User table instead of having a separate table for GpsLocation.
I can not change the JSON Structure because some other No SQL Databases are using this. Also, I'm new to Spring!
The best practice here is using DTO data transfer object that hold the request body
and map it to the user object using external library like mapstruct, ObjectMapper or even do it manually
the DTO is a pojo Object carries data between processes
Try This way with a Constructor:
#Getter
#Setter
#Table(name = "user")
#Entity
public class UnderObservation {
#Column(name = "name", nullable = false)
private String name;
#Id
#Column(name = "id", nullable = false)
private String userID;
#Column(name = "latitude", nullable = false)
private Double latitude;
#Column(name = "longitude", nullable = false)
private Double longitude;
private GpsLocation location;
UnderObservation(String name, String userID, GpsLocation location) {
this.name = name;
this.userID = userID;
this.location = location;
this.latitude = this.location.getLatitude();
this.longitude = this.location.getLongitude();
}
}

how to find objects field [duplicate]

This question already has answers here:
Spring JPA selecting specific columns
(18 answers)
Closed 5 years ago.
I have Location and I want to retrieve all locations in db by company
I can do this in this way List<Location> findByCompanyId(Long companyId);
but How can I retrieve ids of Locations not whole object
List<Long> findByCompanyId(Long companyId);
I want the same thing as in first method but without whole object only ids
public class Location extends BaseEntity {
#Column(name = "`street_first`", nullable = false)
private String streetFirst;
#Column(name = "`street_second`")
private String streetSecond;
#Column(name = "`city`", nullable = false)
private String city;
#Column(name = "`zip`")
private String zip;
#Column(name = "`state`", nullable = false)
private String state;
#Column(name = "`phone`", nullable = false)
private String phone;
#JoinColumn(name = "`company_id`", nullable = false)
private Company company;
}
//getters and setters
#Query("SELECT l.id FROM Location l WHERE l.company.id = :companyId");
List<Long> getLocationIds(#Param("companyId")Long companyId);

Spring data JPA entity change not being persisted

I have a Spring data entity (using JPA w/ Hibernate and MySQL) defined as such:
#Entity
#Table(name = "dataset")
public class Dataset {
#Id
#GenericGenerator(name = "generator", strategy = "increment")
#GeneratedValue(generator = "generator")
private Long id;
#Column(name = "name", nullable = false)
private String name;
#Column(name = "guid", nullable = false)
private String guid;
#Column(name = "size", nullable = false)
private Long size;
#Column(name = "create_time", nullable = false)
private Date createTime;
#OneToOne(optional = false)
#JoinColumn(name = "created_by")
private User createdBy;
#Column(name = "active", nullable = false)
private boolean active;
#Column(name = "orig_source", nullable = false)
private String origSource;
#Column(name = "orig_source_type", nullable = false)
private String origSourceType;
#Column(name = "orig_source_org", nullable = false)
private String origSourceOrg;
#Column(name = "uri", nullable = false)
private String uri;
#Column(name = "mimetype", nullable = false)
private String mimetype;
#Column(name = "registration_state", nullable = false)
private int registrationState;
#OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.ALL})
#JoinColumn(name = "dataset_id")
#JsonManagedReference
private List<DatasetFile> datasetFiles;
I have the following repository for this entity:
public interface DatasetRepo extends JpaRepository<Dataset, Long> {
#Query("SELECT CASE WHEN COUNT(p) > 0 THEN 'true' ELSE 'false' END FROM Dataset p WHERE p.uri = ?1 and p.registrationState>0")
public Boolean existsByURI(String location);
#Query("SELECT a FROM Dataset a LEFT JOIN FETCH a.datasetFiles c where a.registrationState>0")
public List<Dataset> getAll(Pageable pageable);
#Query("SELECT a FROM Dataset a LEFT JOIN FETCH a.datasetFiles c WHERE a.registrationState>0")
public List<Dataset> findAll();
#Query("SELECT a FROM Dataset a LEFT JOIN FETCH a.datasetFiles c where a.guid= ?1")
public Dataset findByGuid(String guid);
}
Now - In a controller, I am fetching a dataset, updating one of its attributes and I would be expecting that attribute change to be flushed to the DB, but it never is.
#RequestMapping(value = "/storeDataset", method = RequestMethod.GET)
public #ResponseBody
WebServiceReturn storeDataset(
#RequestParam(value = "dsGUID", required = true) String datasetGUID,
#RequestParam(value = "stType", required = true) String stType) {
WebServiceReturn wsr = null;
logger.info("stType: '" + stType + "'");
if (!stType.equals("MongoDB") && !stType.equals("Hive") && !stType.equals("HDFS")) {
wsr = getFatalWebServiceReturn("Invalid Storage type '" + stType + "'");
} else if (stType.equals("MongoDB")) {
/* Here is where I'm reading entity from Repository */
Dataset dataset = datasetRepo.findByGuid(datasetGUID);
if (dataset != null) {
MongoLoader mongoLoader = new MongoLoader();
boolean success = mongoLoader.loadMongoDB(dataset);
logger.info("Success: " + success);
if (success) {
/* Here is where I update entity attribute value, this is never flushed to DB */
dataset.setRegistrationState(1);
}
wsr = getWebServiceReturn(success ? 0 : -1, "Successfully loaded dataset files into " + stType + " storage", "Failed to load dataset files into " + stType + " storage");
}
}
return wsr;
}
Thank you
You need to annotate the method of request mapping with #Transactional.
Why? If you want to modify an object in memory and then it is updated transparently in the database you need do it inside an active transaction.
Don't forget you're using JPA (spring-data is using JPA) and if you want your Entity will be in a managed state you need an active transaction.
See:
http://www.objectdb.com/java/jpa/persistence/update
Transparent Update Once an entity object is retrieved from the
database (no matter which way) it can simply be modified in memory
from inside an active transaction:
Employee employee = em.find(Employee.class, 1);
em.getTransaction().begin();
employee.setNickname("Joe the Plumber");
em.getTransaction().commit();

Spring Data Rest 2.1.0 Cannot POST or PUT Complex Resource

EDIT: This appears to be happening with PUTs as well.
Using spring-data-rest-webmvc version 2.1.0.BUILD-SNAPSHOT I have found that I am unable to POST a resource with a relation pointing to an already existing resource. I have 2 such entities which require references to be instantiated and POSTing to either of their endpoints results in the behavior below.
POSTing a resource without required references works well.
I did a bit of digging and it appears that PersistentEntityResourceHandlerMethodArgumentResolver finds the MappingJackson2HttpMessageConverter just fine, but it ends up throwing an exception while checking whether the ObjectMapper can deserialize the type. The cause of the exception is a NullPointerException.
example POST w/ relations to /reservations:
{
"description" : "test_post",
"dateMade" : "2014-03-03T08:04:44.293-0600",
"timeLastChanged" : null,
"userLastChanged" : null,
"courseTypeId" : null,
"numCredits" : null,
"instructor" : null,
"numParticipants" : null,
"reservationType" : "MCU",
"status" : "REQUESTED",
"abstract" : null,
"requestor" : "http://localhost:8080/users/2",
"submitter" : "http://localhost:8080/users/2",
"conferences" : []
}
RESPONSE:
{
cause: null
message: "No suitable HttpMessageConverter found to read request body into object of type class domain.Reservation from request with content type of application/json!"
}
POST w/ no relations to /roomGroups:
{
"description" : "All Rooms",
"isOffNetwork" : false,
"roomGroupType" : "STANDARD"
}
RESPONSE:
201 Created
Is there something wrong about the JSON I am POSTing which is resulting in an NPE from the ObjectMapper? Is there a workaround of some kind? This was working for me in 2.0.0.RC1 using a slightly different scheme for including reference links in the JSON and since the version of the Jackson dependencies appears to have stayed the same I wonder what is causing this issue...
Thanks for any help!
UPDATE:
This issue now seems un-related to the associated entities...
I created a new #Entity ConnectionRequest as follows:
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "CONNECTION_REQUEST_ID")
private Long id;
#Column(name = "FROM_ENTITY_ID", nullable = false)
private Long fromId;
#Column(name = "TO_ENTITY_ID", nullable = false)
private Long toId;
#Convert(converter = EntityTypeConverter.class)
#Column(name = "FROM_ENTITY_TYPE_ID", nullable = false)
private EntityType fromType;
#Convert(converter = EntityTypeConverter.class)
#Column(name = "TO_ENTITY_TYPE_ID", nullable = false)
private EntityType toType;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.MERGE})
#JoinColumn(name = "CONFERENCE_ID", nullable = false)
private Conference conference;
I can POST a new ConnectionRequest record with a Conference relation included in the json as such {"conference" : ".../conferences/1"}.
I am however still getting the same exception w/ this #Entity Reservation:
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "RESERVATION_ID")
private Long id;
#Column(name = "DESCRIPTION", length = 50, nullable = false)
private String description;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "DATE_MADE", nullable = false)
private Date dateMade;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "TIME_LAST_CHANGED")
private Date timeLastChanged;
#Column(name = "USER_LAST_CHANGED")
private Integer userLastChanged; // TODO why is this an int?
#Column(name = "ABSTRACT", length = 2000)
private String _abstract;
#Column(name = "COURSE_TYPE_ID")
private Integer courseTypeId;
#Column(name = "NUMBER_OF_CREDITS")
private Integer numCredits;
#Column(name = "INSTRUCTOR", length = 255)
private String instructor;
#Column(name = "NUMBER_OF_PARTICIPANTS")
private Integer numParticipants;
#Convert(converter = ReservationTypeConverter.class)
#Column(name = "RESERVATION_TYPE_ID", nullable = false)
private ReservationType reservationType;
#Convert(converter = StatusConverter.class)
#Column(name = "STATUS_ID")
private Status status;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.MERGE})
#JoinColumn(name="REQUESTOR_USER_ID", nullable=false)
private User requestor;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.MERGE})
#JoinColumn(name="SUBMITTER_USER_ID", nullable=false)
private User submitter;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "reservation", cascade = {CascadeType.REMOVE})
private Set<Conference> conferences = new HashSet<>();
I'm not sure what's special about this class that's causing things to go awry...
The issue was the following:
Both of the non-postable entities had a property called _abstract due to it being a reserved word in Java. I had named the getter and setter for this property getAbstract() and setAbstract() respectively.
Jackson appears to have been throwing a null pointer exception since the getter and setter did not match the property name as expected.
When I changed the property name to resvAbstract and updated the accessors to getResvAbstract() and setResvAbstract() everything came together and started working.
I'm still curious about the change that led to this issue showing up, but I'm glad it's working!

Resources