Issue with a List<Long> using PlayFramework and JPA - java

I am working on a project using the Play!Framework, and I an encoutering some issues with JPA. It seems that it doesn't save my List, or doesn't manage to retrieve it from the DB.
Here is my User class :
#Entity
public class User extends Model {
public String login;
public String password;
public String name;
public String email;
#ElementCollection
public List<Long> eventStreamIds;
#Transient
UserEventBuffer eventBuffer;
public User(String login, String password, String name, String email, ArrayList<Long> eventStreamIds) {
this.login = login;
this.password = password;
this.name = name;
this.email = email;
this.eventStreamIds = eventStreamIds;
UserEventBuffer eventBuffer = new UserEventBuffer();
}
}
I have another class, a ModelManager, which constructor is as following :
public ModelManager() {
User u = new User("user1", "pwd", "Alex", "test#test.com");
EventStreamMC eb = new EventStreamMC("http://www.wservice.com/stream1");
streams.add(eb);
u.eventStreamIds.add(eb.id);
Logger.info("before : " + u.eventStreamIds.size());
u.save();
User u2 = User.find("byLoginAndPassword", "user1", "pwd").first();
Logger.info("after : " + u2.eventStreamIds.size());
}
(My EventStreamMC also extends Model and has an #Entity tag, so its Long id is automatically generated)
When i run this code, here is the result :
before = 1
after = 0
So the List is empty after the call to the find() method.
How can I make it work ?

ID are generated only on save and eb is never saved in your code. ed.id will be empty when you add it to your list.
EDIT :
I'll go a little further in my explanations.
The first logger is returning 1 since it is a Java List Object. It is a plain list, you add an element and the size increases. No surprises, here. Plain java. And absolutly nothing to do with Play!.
Second logger is not displaying anything since ID are generated on commit precisly. And since, you are still in the same transaction, no save is performed until the very end of the execution. If you really want this to work in the same http request/transaction, use this :
Model.em().getTransaction().commit();
For more information, check out official Play documentation on Transactions.

Related

JPA-Repository save: Proceed saving after insert Constraint violation

i'm using JPA repository to save simple data objects to the database. To avoid duplicates i created a unique constraint on multiple fields. If now a duplicate according to the unique fields/constraint should be saved i want to catch the exception, log the object and the application should proceed and saves the next object. But here i always get this exception: "org.hibernate.AssertionFailure: null id in de.test.PeopleDBO entry (don't flush the Session after an exception occurs)".
In general i understand what hibernate is doing, but how i can revert the session or start a new session to proceed with saving of the next data objects. Please have a look to the code below:
PeopleDBO.java
#Entity
#Data
#Table(
name = "PEOPLE",
uniqueConstraints = {#UniqueConstraint(columnNames = {"firstname", "lastname"}})
public class PeopleDBO {
public PeopleDBO(String firstname, String lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstname;
private String lastname;
}
The Test:
public void should_save_people_and_ignore_constraint_violation(){
final List<PeopleDBO> peopleList = Arrays.asList(
new PeopleDBO("Georg","Smith"),
new PeopleDBO("Georg","Smith"),
new PeopleDBO("Paul","Smith")
);
peopleList.forEach(p -> {
try {
peopleRepository.save(p);
} catch (DataIntegrityViolationException e) {
log.error("Could not save due to constraint violation: {}",p);
}
}
Assertions.assertThat(peopleRepository.count()).isEqualTo(2);
}
The problem is, that with saving of the second people the unique constraint gets violated. The error log happens, and with the next call of peopleRepository.save() the mentioned exception above is thrown:
"org.hibernate.AssertionFailure: null id in de.test.PeopleDBO entry (don't flush the Session after an exception occurs)"
How i can avoid this behaviour? How i can clean the session or start a new session?
Thanks a lot in advance
d.
--------- Edit / new idea ------
I just tried some things and have seen that i could implement a PeopleRepositoryImpl, like this:
#Service
public class PeopleRepositoryImpl {
final private PeopleRepository peopleRepository;
public PeopleRepositoryImpl(PeopleRepository peopleRepository) {
this.peopleRepository = peopleRepository;
}
#Transactional
public PeopleDBO save(PeopleDBO people){
return peopleRepository.save(people);
}
}
This is working pretty fine in my tests. ... what do you think?
One single transaction
The reason is that all inserts occur in one transaction. As this transaction is atomic, it either succeeds entirely or fails, there is nothing in-between.
The most clean solution is to check if a People exists before trying to insert it:
public interface PeopleRespository {
boolean existsByLastnameAndFirstname(String lastname, String firstname);
}
and then:
if (!peopleRepository.existsByLastnameAndFirstname(p.getLastname, p.getFirstname)) {
peopleRepository.save(p);
}
One transaction per people
An alternative is indeed to start a new transaction for each person. But I am not sure it will be more efficient, because there is an extra cost to create transaction.

Spring data mongodb query add boolean field if value exists in array

I'm trying to query some data from mongoDb which contains likes array. Each like object holds user.id of liker.
I need an extra boolean field that says if document is liked by user or not. So I need isLiked to be true if user has liked the document.
Here is what I have done till now:
I used ConditionalOperators.Cond to check if likes.userId is equal to userId of visitor.
#Override
public List<PostExtra> findPostsNearBy(double[] point, Distance distance, String thisUserId) {
mongoTemplate.indexOps(CheckInEntity.class).ensureIndex(new GeospatialIndex("position"));
ConditionalOperators.Cond conditionalOperators = new ConditionalOperators.ConditionalOperatorFactory(Criteria.where("likes.userId").is(thisUserId)).then(true).otherwise(false);
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.geoNear(
locationBasedOperationHelper.findNear(point,distance)
.query(new Query(privacyConsideredOperationHelper.privacyConsideredQuery(userRelationsEntity)))
,"distance"
),
//Aggregation.unwind("likes"),
Aggregation.project("user", "description").and(conditionalOperators).as("isLiked")
);
final AggregationResults<PostExtra> results =
mongoTemplate.aggregate(aggregation, PostEntity.class, PostExtra.class);
return results.getMappedResults();
}
if I remove the comment on Aggregation.unwind("likes") I can only get posts that this user has liked not those he hasn't.
I have seen the same matter here but I dont know whats the MontoTemplate code related to that?
Also I have seen approaches with setIsSubset, still I dont know java implementation.
I'm using spring boot 2.0.4.RELEASE and spring-boot-starter-data-mongodb.
#Document(collection = EntityCollectionNames.POST_COLLECTION_NAME)
public class PostEntity{
#Id
private String id;
#Field
#DBRef
#Indexed
private UserEntity user;
#GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)
#Field(value = "position")
private Point position;
#Field(value = "description")
private String description;
#Field
private int likesCount;
#Field
private List<LikeEntity> likes;
}
Post Extra:
public class PostExtra extends PostEntity {
private double distance;
private boolean isLiked;
}
Like:
public class LikeEntity {
#Field
private String userId;
}
After some searching I found out to do it without using unwind I need to implement something like this to project isLiked field:
$cond:[
{$gt:[
{$size:
{$setIntersection:["$likes.userId",userIdsToCheckIn]}
},0]
},true,false]
I tried to do it in spring like this:
ConditionalOperators.Cond conditionalOperators = new ConditionalOperators.ConditionalOperatorFactory(
ComparisonOperators.Gt.valueOf(
ArrayOperators.Size.lengthOfArray(
SetOperators.SetIntersection.arrayAsSet("$likes.userId").intersects(????????)
)
).greaterThanValue(0)
).then(true).otherwise(false);
But I didnt know what to pass to intersects() method and apparently Spring does not let you pass List or Array as that input. Then I found out I can implement my own AggregationExpressions and override toDocument() method.
So I came up with this and it worked:
ConditionalOperators.Cond conditionalOperators = new ConditionalOperators.ConditionalOperatorFactory(
new AggregationExpression() {
#Override
public Document toDocument(AggregationOperationContext aggregationOperationContext) {
return Document.parse("{$gt:[{$size:{$setIntersection:[\"$likes.userId\",[\""+userId+"\"]]}},0]}");
}
}
).then(true).otherwise(false);
Ofcourse I could add $cond to the implementation directly too.
I would still like to know if there are any spring solutions that does not contain mongodb queries directly for this.

Play Framework - How to map web data to object data

Imagine the following scenario:
I send a request to a service (which uses Play framework) with the following parameters (parameter's name should be underscored by convention):
first_name=James&second_name=Parker
Moreover I have a model class in my codebase which looks like this.
public class User {
#Constraints.Required
private String firstName;
#Constraints.Required
private String secondName;
public String getFirstName() {
return firstName;
}
public String getSecondName() {
return secondName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setSecondName(String secondName) {
this.secondName = secondName;
}
}
All I want to do is to map parameter's names with the corresponding
field names. The following statement using Play Framework fails because
User object cannot be initialized with the given parameters of request.
Form<User> form = Form.form(User.class).bindFromRequest();
Read this first: https://www.playframework.com/documentation/2.1.1/JavaRouting
And then:
GET /myMethod/:firstName/:seccondName controllers.MyController.myMethod(firstName: String, seccondName: String)
and simple Method:
public myMethod(String firstName, String lastName) {
User u = new User();
u.setFirstName(firstName);
u.setSeccondName(seccondName);
}
Obviously if you use play framework 2.
You have underscores in the request parameter names, yet your class has the members in camelCase (e.g. first_name vs fistName [Missing a r here]).
Secondly it also seems like these class members are declared private AND you have no setter methods only getters (i.e. setFirstName).
In order to do the binding your class is instantiated and the values are set and with no way to do so it will fail.
Once you've fixed the above, you should be able to bind and then call form.hasErrors() to check if validation has failed.
Hope that helps.
Update:
To handle the mismatch between the request parameter names and the class member names you would probably have to manually set things up.
In your controller method you would do something like this:
Map<String, String[]> reqBody = request().body().asFormUrlEncoded()
Map<String, String[]> newReqBody = new HashMap<>();
for(Map.Entry<String, String[]> entry: body.entrySet()) {
newBody.put(underscoreToCamelCase(entry.key()), entry.value());
}
Form<User> form = Form.form(User.class).bindFromRequest(newReqBody);
Note that I'm using the overloaded version of bindFromRequest as seen here
You should then implement the underscoreToCamelCase method in a generic enough way to handle all your conventions (Perhaps you might have a situation where there are more than one underscores).

Removing simple Relationships in Spring Data Neo4j

Good Evening,
I am using SDN 3, and am running into problems with removing simple relationships (RelateTo) in my underlying graph. The scenario is that I want to establish a Friend request/approval system amongst Users in my web application. I have no problem issuing the request by creating a "HAS_REQUESTED" relationship between Users. but once the User receiving the friend request hits "approve", the "FRIENDS_WITH" relationship is established without properly removing the "HAS_REQUESTED" relationship. the code below walks through the process:
The relevant Controller method
#RequestMapping(value="/approve/friend/{friendId}")
public String approveFriend(#PathVariable("friendId") String friendId){
User me = userService.findByEmail(userService.getAuthenticatedUser().getName());
userService.removeOldRequests(friendId, me);
userService.approveFriendship(friendId, me);
return "redirect:/friends";
}
The UserService method in question. 'me' is the authenticated user who originally sent the friend request to 'friendId/friend':
public void removeOldRequests(String friendId, User me){
try{
User friend = userRepository.findByUserId(friendId);
friend.addStartNodeForUsersRequestingMe(me, false);
template.save(friend);
}catch(Exception e){
e.printStackTrace();
}
and here is my User entity Node (excluding unrelated fields/getters/setters.)
#NodeEntity
public class User {
#GraphId Long nodeId;
#Indexed
String userId;
String username;
String firstName;
String lastName;
String email;
String aboutMe;
String Quote;
String favoriteBook;
int age;
Date userCreation;
String sex;
String password;
Role role;
byte[] picture;
#RelatedTo(type="FRIENDS_WITH", direction=Direction.BOTH)
#Fetch
Set<User> friends;
#RelatedTo(type="HAS_FRIEND_REQUEST")
#Fetch
Set<User> startNodeForUsersRequestingMe;
#RelatedTo(type="HAS_FRIEND_REQUEST", direction=Direction.INCOMING)
#Fetch
Set<User> UsersWhoHaveRequestedMe;
public void addStartNodeForUsersRequestingMe(User user, boolean flag){
if(flag){
this.startNodeForUsersRequestingMe.add(user);
}else{
this.startNodeForUsersRequestingMe.remove(user);
}
}
public void addUsersWhoHaveRequestedMe(User user, boolean flag){
if(flag){
this.UsersWhoHaveRequestedMe.add(user);
}else{
this.UsersWhoHaveRequestedMe.remove(user);
}
}
The repository method I am using to return the current user's friend requests is below. Right now it is configured to just return any relationship the user has that is "HAS_FRIEND_REQUEST" just for testing purposes to see if I can get User A with one friend request from User B to NOT be returned.
#Query("START user=node({0})"
+"MATCH (user)-[:HAS_FRIEND_REQUEST]-(friend)"
+ "return friend;")
Iterable getUserFriendRequests(User user);
Any guidance on how to properly remove the "HAS_FRIEND_REQUEST" in a clean manner would be greatly appreciated. either that, or maybe a better way to handle the "friend request Handshake" idea. If you have any questions or concerns for me, please do not hesitate to bring them to my attention.
You can remove the target user from the collection
or use the Neo4jTemplate method to delete the relationship.
template.deleteRelationshipBetween(Object start, Object end, String type)

How do I update an entity using spring-data-jpa?

Well the question pretty much says everything. Using JPARepository how do I update an entity?
JPARepository has only a save method, which does not tell me if it's create or update actually. For example, I insert a simple Object to the database User, which has three fields: firstname, lastname and age:
#Entity
public class User {
private String firstname;
private String lastname;
//Setters and getters for age omitted, but they are the same as with firstname and lastname.
private int age;
#Column
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
#Column
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
private long userId;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
public long getUserId(){
return this.userId;
}
public void setUserId(long userId){
this.userId = userId;
}
}
Then I simply call save(), which at this point is actually an insert into database:
User user1 = new User();
user1.setFirstname("john"); user1.setLastname("dew");
user1.setAge(16);
userService.saveUser(user1);// This call is actually using the JPARepository: userRepository.save(user);
So far so good. Now I want to update this user, say change his age. For this purpose I could use a Query, either QueryDSL or NamedQuery, whatever. But, considering I just want to use spring-data-jpa and the JPARepository, how do I tell it that instead of an insert I want to do an update?
Specifically, how do I tell spring-data-jpa that users with the same username and firstname are actually EQUAL and that the existing entity supposed to be updated? Overriding equals did not solve this problem.
Identity of entities is defined by their primary keys. Since firstname and lastname are not parts of the primary key, you cannot tell JPA to treat Users with the same firstnames and lastnames as equal if they have different userIds.
So, if you want to update a User identified by its firstname and lastname, you need to find that User by a query, and then change appropriate fields of the object your found. These changes will be flushed to the database automatically at the end of transaction, so that you don't need to do anything to save these changes explicitly.
EDIT:
Perhaps I should elaborate on overall semantics of JPA. There are two main approaches to design of persistence APIs:
insert/update approach. When you need to modify the database you should call methods of persistence API explicitly: you call insert to insert an object, or update to save new state of the object to the database.
Unit of Work approach. In this case you have a set of objects managed by persistence library. All changes you make to these objects will be flushed to the database automatically at the end of Unit of Work (i.e. at the end of the current transaction in typical case). When you need to insert new record to the database, you make the corresponding object managed. Managed objects are identified by their primary keys, so that if you make an object with predefined primary key managed, it will be associated with the database record of the same id, and state of this object will be propagated to that record automatically.
JPA follows the latter approach. save() in Spring Data JPA is backed by merge() in plain JPA, therefore it makes your entity managed as described above. It means that calling save() on an object with predefined id will update the corresponding database record rather than insert a new one, and also explains why save() is not called create().
Since the answer by #axtavt focuses on JPA not spring-data-jpa
To update an entity by querying then saving is not efficient because it requires two queries and possibly the query can be quite expensive since it may join other tables and load any collections that have fetchType=FetchType.EAGER
Spring-data-jpa supports update operation.
You have to define the method in Repository interface.and annotated it with #Query and #Modifying.
#Modifying
#Query("update User u set u.firstname = ?1, u.lastname = ?2 where u.id = ?3")
void setUserInfoById(String firstname, String lastname, Integer userId);
#Query is for defining custom query and #Modifying is for telling spring-data-jpa that this query is an update operation and it requires executeUpdate() not executeQuery().
You can specify the return type as int, having the number of records being updated.
Note: Run this code in a Transaction.
You can simply use this function with save() JPAfunction, but the object sent as parameter must contain an existing id in the database otherwise it will not work, because save() when we send an object without id, it adds directly a row in database, but if we send an object with an existing id, it changes the columns already found in the database.
public void updateUser(Userinfos u) {
User userFromDb = userRepository.findById(u.getid());
// crush the variables of the object found
userFromDb.setFirstname("john");
userFromDb.setLastname("dew");
userFromDb.setAge(16);
userRepository.save(userFromDb);
}
As what has already mentioned by others, the save() itself contains both create and update operation.
I just want to add supplement about what behind the save() method.
Firstly, let's see the extend/implement hierarchy of the CrudRepository<T,ID>,
Ok, let's check the save() implementation at SimpleJpaRepository<T, ID>,
#Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
As you can see, it will check whether the ID is existed or not firstly, if the entity is already there, only update will happen by merge(entity) method and if else, a new record is inserted by persist(entity) method.
spring data save() method will help you to perform both: adding new item and updating an existed item.
Just call the save() and enjoy the life :))
Using spring-data-jpa save(), I was having same problem as #DtechNet. I mean every save() was creating new object instead of update. To solve this I had to add version field to entity and related table.
This is how I solved the problem:
User inbound = ...
User existing = userRepository.findByFirstname(inbound.getFirstname());
if(existing != null) inbound.setId(existing.getId());
userRepository.save(inbound);
With java 8 you can use repository's findById in UserService
#Service
public class UserServiceImpl {
private final UserRepository repository;
public UserServiceImpl(UserRepository repository) {
this.repository = repository;
}
#Transactional
public void update(User user) {
repository
.findById(user.getId()) // returns Optional<User>
.ifPresent(user1 -> {
user1.setFirstname(user.getFirstname);
user1.setLastname(user.getLastname);
repository.save(user1);
});
}
}
public void updateLaserDataByHumanId(String replacement, String humanId) {
List<LaserData> laserDataByHumanId = laserDataRepository.findByHumanId(humanId);
laserDataByHumanId.stream()
.map(en -> en.setHumanId(replacement))
.collect(Collectors.toList())
.forEach(en -> laserDataRepository.save(en));
}
Specifically how do I tell spring-data-jpa that users that have the
same username and firstname are actually EQUAL and that it is supposed
to update the entity. Overriding equals did not work.
For this particular purpose one can introduce a composite key like this:
CREATE TABLE IF NOT EXISTS `test`.`user` (
`username` VARCHAR(45) NOT NULL,
`firstname` VARCHAR(45) NOT NULL,
`description` VARCHAR(45) NOT NULL,
PRIMARY KEY (`username`, `firstname`))
Mapping:
#Embeddable
public class UserKey implements Serializable {
protected String username;
protected String firstname;
public UserKey() {}
public UserKey(String username, String firstname) {
this.username = username;
this.firstname = firstname;
}
// equals, hashCode
}
Here is how to use it:
#Entity
public class UserEntity implements Serializable {
#EmbeddedId
private UserKey primaryKey;
private String description;
//...
}
JpaRepository would look like this:
public interface UserEntityRepository extends JpaRepository<UserEntity, UserKey>
Then, you could use the following idiom: accept DTO with user info, extract name and firstname and create UserKey, then create a UserEntity with this composite key and then invoke Spring Data save() which should sort everything out for you.
As mentioned by others answer, method save() is dual function. It can both do save or update, it's automatically update if you provide the id.
for update method in controller class I suggested to use #PatchMapping. below is the example.
#Save method POST
{
"username": "jhon.doe",
"displayName": "Jhon",
"password": "xxxyyyzzz",
"email": "jhon.doe#mail.com"
}
#PostMapping("/user")
public void setUser(#RequestBody User user) {
userService.save(user);
}
#Update method PATCH
{
"id": 1, // this is important. Widly important
"username": "jhon.doe",
"displayName": "Jhon",
"password": "xxxyyyzzz",
"email": "jhon.doe#mail.com"
}
#PatchMapping("/user")
public void patchUser(#RequestBody User user) {
userService.save(user);
}
Maybe you're wondering where the id's come from. It comes from the database of course, you want to update the existing data right?
If your primary key is autoincrement then, you have to set the value for the primary key.
for the save(); method to work as a update().else it will create a new record in db.
if you are using jsp form then use hidden filed to set primary key.
Jsp:
<form:input type="hidden" path="id" value="${user.id}"/>
Java:
#PostMapping("/update")
public String updateUser(#ModelAttribute User user) {
repo.save(user);
return "redirect:userlist";
}
also look at this:
#Override
#Transactional
public Customer save(Customer customer) {
// Is new?
if (customer.getId() == null) {
em.persist(customer);
return customer;
} else {
return em.merge(customer);
}
}
Use #DynamicUpdate annotation. it is cleaner and you don't have to deal with querying the database in order to get the saved values.
You can see the example below:
private void updateDeliveryStatusOfEvent(Integer eventId, int deliveryStatus) {
try {
LOGGER.info("NOTIFICATION_EVENT updating with event id:{}", eventId);
Optional<Event> eventOptional = eventRepository.findById(eventId);
if (!eventOptional.isPresent()) {
LOGGER.info("Didn't find any updatable notification event with this eventId:{}", eventId);
}
Event event = eventOptional.get();
event.setDeliveryStatus(deliveryStatus);
event = eventRepository.save(event);
if (!Objects.isNull(event)) {
LOGGER.info("NOTIFICATION_EVENT Successfully Updated with this id:{}", eventId);
}
} catch (Exception e) {
LOGGER.error("Error :{} while updating NOTIFICATION_EVENT of event Id:{}", e, eventId);
}
}
Or Update Using Native Query:
public interface YourRepositoryName extends JpaRepository<Event,Integer>{
#Transactional
#Modifying
#Query(value="update Event u set u.deliveryStatus = :deliveryStatus where u.eventId = :eventId", nativeQuery = true)
void setUserInfoById(#Param("deliveryStatus")String deliveryStatus, #Param("eventId")Integer eventId);
}
I did this for my Entity UserModel:
In the Controller:
#PutMapping("/{id}")
public Optional<UserModel> update(#RequestBody UserModel user, #PathVariable Long id) {
return this.userService.update(user, id);
}
And in the Service:
public Optional<UserModel> update(UserModel req, Long id){
Optional<UserModel> user = userRepository.findById(id);
if (user != null) {
userRepository.save(req);
}
return user;
}
Example with postman:
Postman method PUT example

Categories

Resources