Hello everyone and thank you in advance for looking at this question. I am using a Couchbase Server community-6.6.0 on Java Spring Boot (Java JDK version 1.8).
Problem Description: To interact with Couchbase I am using a PagingAndSortingRepository Repository. Each time I call .save() a new document representing a new instance of the Object I am using gets created in Couchbase. Also, when I retrieve an Object from Couchbase change the value of some field e.g., .setPassword() and then call .save() to make the change persistent then I get a new document (with a new Id) of that Object is created in Couchbase. It seems to me that many different revisions of that Object exist in the Database when I call .save(), I want to make sure that I am always working on the latest version of that instance of my Object/Entity.
I am really confused! What I am trying to do should be straightforward, every time I update a Java Entity I want the changes to be made persistent for that Document (with that Document id) in Couchbase. Perhaps I am missing something here. Please advice.
Partial view of the pom looks like:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-couchbase</artifactId>
</dependency>
Entities as follows:
#Data
#Document
#CompositeQueryIndex(fields = {"id", "userName"})
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#IdPrefix
private String prefix;
#Id #NotNull #GeneratedValue( delimiter = "::", strategy = GenerationStrategy.UNIQUE)
private String id;
#Field
#NotNull
#Size(min=2, max=40)
private String firstname;
#Field
#NotNull
#QueryIndexed
private String username;
//Other attributes here ...
}
//ConfirmationToken Entity
#Data
#NoArgsConstructor
#CompositeQueryIndex(fields = {"id", "confirmationToken"})
public class ConfirmationToken {
#IdPrefix
private String prefix = "token";
#Id #NotNull #GeneratedValue(delimiter = "::", strategy = GenerationStrategy.UNIQUE)
private String id;
#Field
#Reference
private User user;
#Field
#QueryIndexed
private String confirmationToken;
//Other attributes here ...
}
UserRepository looks like:
#Repository
public interface UserRepository extends PagingAndSortingRepository<User, Long> {
#ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
List<User> findByUsername(String username);
}
#Service
#AllArgsConstructor
public class ConfirmationTokenServiceImpl implements ConfirmationTokenService {
#Autowired
private final ConfirmationTokenRepository confirmationTokenRepository;
#Override
public void saveConfirmationToken(ConfirmationToken confirmToken) {
confirmationTokenRepository.save(confirmToken);
}
}//end class
When I want to retrieve the User Object from Couchbase I do the following:
//How do I ensure this Object is the latest version of that document?
List<User> users = userRepository.findByUsername(username);
//The following does not ensure that I get the latest version of that User document with that username
User testUser = users.get(users.size()-1);
In some method I change field values of that Java Object i.e., User and then a new revision of that User Document gets created in the Database when I call .save(User Object). How do I update the fields of that User object and make that Document persistent in the Database without creating a new revision of that Document? Also, how do I ensure I always get and work on the latest version of that User Document?
public void someMethod(UserDTO regForm, final String contextPath) {
User testUser = new User();
testUser.setFirstname(regForm.getFname());
testUser.setLastname(regForm.getSname());
//Make User persistent
userRepository.save(testUser);
//Create the confirmation token for that User instance
final ConfirmationToken confirmToken = new ConfirmationToken(testUser);
confirmationTokenService.saveConfirmationToken(confirmToken);
}
In this Method I have another Class called ConfirmationToken that has a reference to a User Object. This is to associate each ConfirmationToken instance with a specific User Instance. When I call the following method to change the User password a new document with a new id is created for the User in the Database. I cannot understand how to update the current instance. Please help.
public void changeUserPassword(ConfirmationToken confirmToken, ResetPassDTO resetPassForm) {
//Retrieve the User instance that is associated with that Token
final User testUser = confirmToken.getUser();
if (testUser.getEmail().equals(resetPassForm.getEmail())) {
//Get the Hash of the User's password
final String encryptedPassword = bCryptPasswordEncoder.encode(resetPassForm.getNewPassword());
testUser.setPassword(encryptedPassword);
//Enable the user account - since email ownership is also verified
testUser.setIsEnabled(true);
userRepository.save(testUser);
}
//Delete the Confirmation Token entity
confirmationTokenService.deleteConfirmationToken(confirmToken);
}
Update:
It seems that I had to go back and work on my domain modelling for the Entities. More specifically to think of how couchbase is doing docs embedding and when to do referencing. In my example, the ConfirmationToken instance holds a reference to a User instance. In reality the ConfirmationToken embeds the User instance in the same JSON doc. I cannot access the actual User object I want though. In other words, it seems that I cannot dereference and get the User instance by following the id of User object from the ConfirmationToken instance. My solution: I introduced a uuid field in the User object, I then get the reference for a User instance from ConfirmationToken, then I call userRepository.findByUuid(userFromToken.getUuid()); only then I was able to get the actual User object that I wanted. My question is why Couchbase spring SDK cannot do this dereferencing automatically? Also, it seems that when I change the value of some attribute the change does not happen on the actual object but on the embedding, so every time I need to do this dereferencing to get to the real object manually. This is similar to MongoDB #DBRefs?? Not sure how if there is a solution with N1QL though and NEST queries? Can I achieve this with N1QL?
Related
I'm currently learning Spring-Boot and Spring-Data-JPA.
I'm using a postgresql database for storing the data.
My goal is to store ingredients with a unique and custom ID (you just type it in when creating it), but when another ingredient with the same ID gets inserted, there should be some kind of error. In my understanding, this is what happens when I use the #Id annotation, hibernate also logs the correct create table statement.
This is my Ingredient class:
public class Ingredient {
#Id
#Column(name = "ingredient_id")
private String ingredient_id;
#Column(name = "name")
private String name;
#Column(name = "curr_stock")
private double curr_stock;
#Column(name = "opt_stock")
private double opt_stock;
#Column(name = "unit")
private String unit;
#Column(name = "price_per_unit")
private double price_per_unit;
#Column(name = "supplier")
private String supplier;
-- ... getters, setters, constructors (they work fine, I can insert and get the data)
}
My controller looks like this:
#RestController
#RequestMapping(path = "api/v1/ingredient")
public class IngredientController {
private final IngredientService ingredientService;
#Autowired
public IngredientController(IngredientService ingredientService) {
this.ingredientService = ingredientService;
}
#GetMapping
public List<Ingredient> getIngredients(){
return ingredientService.getIngredients();
}
#PostMapping
public void registerNewStudent(#RequestBody Ingredient ingredient) {
ingredientService.saveIngredient(ingredient);
}
}
And my service class just uses the save() method from the JpaRepository to store new ingredients.
To this point I had the feeling, that I understood the whole thing, but when sending two post-requests to my application, each one containing an ingredient with the id "1234", and then showing all ingredients with a get request, the first ingredient just got replaced by the second one and there was no error or smth. like that in between.
Sending direct sql insert statements to the database with the same values throws an error, because the primary key constraint gets violated, just as it should be. Exactly this should have happened after the second post request (in my understanding).
What did I get wrong?
Update:
From the terminal output and the answers I got below, it is now clear, that the save() method can be understood as "insert or update if primary key is already existing".
But is there a better way around this than just error-handle every time when saving a new entry by hand?
The save method will create or update the entry if the id already exists. I'd switch to auto generating the ID when inserting, instead of manually creating the IDs. That would prevent the issue you have
When saving a new ingredient, jpa will perform an update if the value contained in the “id” field is already in the table.
A nice way through which you can achieve what you want is
ingredientRepository.findById(ingredientDTO.getIngredientId()).
ifPresentOrElse( ingredientEntity-> ResponseEntity.badRequest().build(), () -> ingredientRepository.save(ingredientDTO));
You can return an error if the entity is already in the table otherwise (empty lambda), you can save the new row
This is a downside to using CrudRepository save() on an entity where the id is set by the application.
Under the hood EntityManager.persist() will only be called if the id is null otherwise EntityManager.merge() is called.
Using the EntityManager directly gives you more fine grained control and you can call the persist method in your application when required
class Address {
String address1, country, state, zip;
}
class Foo {
#Field(type = FieldType.Object)
Address work;
boolean workAddressSameAsHome;
#Field(type = FieldType.Object)
Address home;
}
I would like to return work value as home in the JSON response if workAddressSameAsHome=true, as the value will not be stored in ES. How do I make this work for a GET request /foo/<id>
Spring Data Elasticsearch does not offer any REST URLs, so I assume you are using Spring Data Rest on top.
BTW, your Foo class has no #Document and #Id property, I guess your real class has one.
What you can do in Spring Data Elasticsearch is to provide a bean that implements the AfterConvertCallback interface - available since Spring Data Elasticsearch 4.0:
#Component
public class SetWorkAddressAsHomeCallback implements AfterConvertCallback<Foo> {
#Override
public Foo onAfterConvert(Foo foo, Document document, IndexCoordinates indexCoordinates) {
if (foo.workAddressSameAsHome) {
foo.home = foo.work;
}
return foo;
}
}
This callback is invoked after the document is read from Elasticsearch. Here I just copy the work address to the home address field.
For more information about the callbacks check the documentation at https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#elasticsearch.entity-callbacks
You might consider to implement a BeforeConvertCallback as well where you would clear the home address when the workAddressSameAsHome is true, this would be invoked before the entity is saved in Elasticsearch, thus preventing to store a home address in this case. But keep in mind that you won't be able to do an Elasticsearch search for the home address in this case.
I have a class that has users information including the password field. When the user login, it will return everything from class including password. How do I not return everything from class except the password or any important data that only remain in the database.
I tried using the Map this also returns the way I want but I was hoping if there is something easier or quicker then Map.
There are few answers suggesting using JsonIgnore and transient. If I use these two methods, I am not able to login. Because I need password back for login.
My POJO Class
#Entity
public class Users {
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "firstname")
private String firstName;
#Column(name = "lastname")
private String lastName;
#Id
#Column(name = "username")
private String username;
#Column(name = "email")
private String email;
#Column(name = "role")
private String role;
#Column(name = "password")
private String password;
Repo Class
public interface UsersRepository extends CrudRepository<Users,
String> {
public Users findByUsername(String username);
}
this is Rest Api
#GetMapping("/users/{username}")
public Map<String, Object> usersCheck(#PathVariable String
username) {
Map<String, Object> addUser = new HashMap<>();
Users user = userRepo.findByUsername(username);
addUser.put("email", user.getEmail());
addUser.put("firstName",user.getFirstName());
"
"
return addUser;
}
Is there a better way then Map. Any suggestion can be helpful.
Actually there is a way in jpa queries To return only specific field so you can use directly while fetching the results.
But in case if you don't want to disturb the findByUsername method than just create an object of User class and set only desired fields.
The approach you are using currently is also feasible solution.
The simplest way is to control the serialization to JSON via the annotations provided by the default JSON library which is Jackson.
https://fasterxml.github.io/jackson-annotations/javadoc/2.5/com/fasterxml/jackson/annotation/JsonIgnore.html
#JsonIgnore
#Column(name = "password")
private String password;
You can also do this via a Jackson mixin to avoid 'polluting' the entity with JSON processing instructions.
So, there are 2 different ways you can approach this problem.
Make password field transient in your Entity class.
This way, when you fetch the Users object, password field would be blank.
Disadvantage : Making password transient would result in the fact that you would not be able to get password via you entity anywhere in your application.
Use #JsonIgnore(from jackson library) over password field. This way, when you return the object of Users object, password field would be ignored.
Disadvantage : This would again means that if ever you want to take password field as input or return password field through out the application you would not be able to do so.
Also, it is not recommended that you return object of your POJO class ever as response.
So, you can go with either one keeping in mind the disadvantages each approach has.
Apart from already mentioned reply, few other ways are also there such as JsonIgnoreProperties, JsonIgnoreType, JsonFilter. I prefer JsonIgnore for suppressing the field in the output.
Here is a link to nice example
https://www.baeldung.com/jackson-ignore-properties-on-serialization
Also, you can always create a separate POJO class to return desire values.
The typical best practice here is to treat the password as a subresource: logically not a direct part of the User resource, but related to it (e.g., it might have its own URL at /users/{id}/password). Spring Data REST handles this automatically when you have a JPA #OneToOne relationship, but there's no problem with doing it yourself.
This example shows why it is not a good idea to use your #Entity classes directly as the JSON API representations, because you may want to have differences internally (including making changes in the future without disturbing clients). Instead, use a data transfer object (DTO) that serves as a "JSON copy" of your entity. Tools like MapStruct make it very simple to copy properties between User and UserDto.
(Finally, if you do find yourself needing to return a bare Map for some odd reason, which does happen, it's typically best to use Map.of("key", value) for simplicity.)
Another solution could be create a method in repository with #Query annotation like:
#Query("SELECT NEW MyEntity(M.firstName, M.lastName, M.email) FROM MyEntity M WHERE M.username = ?1")
public MyEntity getByUsername(String username);
Then in your MyEntity class create a constructor that matching with query's constructor.
By last, in your controller:
Instead of:
public Map<String, Object> usersCheck(#PathVariable String
username)
Do:
public MyEntity usersCheck(#PathVariable String
username){
return userRepo.getByUsername(username);
}
Return directly cause spring have a naturally integration with Jackson serialization so by default your response will be a json object
I think this solution is a good alternative in your case.
If you want to exclude password from the response then annotate the password field with #JsonIgnore.
If you want to exclude more than one field in the User entity then create an UserDto class and add the required field in that UserDto class.
Use ModelMapper to map the User entity to UserDto class. Finally return this UserDto class as a response object.
Example:
User user = new User();
UserDto userDto = new ModelMapper.map(user, UserDto.class);
This will include the fields in UserDto only
User Model
#Entity
#Table(name="user")
public class User extends Model
{
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
public int id;
public String name;
public int age;
}
In my controller, trying to update the user submitted form
Form<User> userForm = Form.form(User.class).bindFromRequest();
Updating one of the property of form object, using following snippet.
userForm.data().put("name","updated_name"); // Trying to update name field.
Verifying, whether the change is reflected or not. Actually it's getting updated by verifying its log.
Logger.info("submitted form :: "+userForm.toString()); // Form(of=class models.User, data={name=updated_name, age=25}, value=Some(models.User#768d00), errors={})
User user = userForm.get();
// But here it returning only the original value.
Logger.info("name :: "+user.name); // Getting Old name.
For updating the name property in my controller i can directly update like following
user.name = "updated_name", but i just want to know why the above approach not working?
I use Achilles library for working with cassandra database. The problem is when I create entity method that effects fields Achilles do not "see" these changes. See example below.
import info.archinnov.achilles.persistence.PersistenceManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
public class AhilesTest {
private static final UUID ID = UUID.fromString("083099f6-e423-498d-b810-d6c564228724");
//This is achilles persistence manager
#Autowired
private PersistenceManager persistenceManager;
public void test () {
//user creation and persistence
User toInsert = new User();
toInsert.setId(ID);
toInsert.setName("name");
toInsert.setVersion(0l);
persistenceManager.insert(toInsert);
//find user
User user = persistenceManager.find(User.class, id);
user.changeName("newName");
persistenceManager.update(user);
User updatedUser = persistenceManager.find(User.class, id);
//here old "name" value is returned
updatedUser.getName();
}
public class User {
private UUID id;
private String name;
private long version;
public void changeName (String newName) {
this.name = newName;
this.version++;
}
//getters and setters are omited
}
}
user.changeName("newName"); do not affect entity and "old" values are persisted. For my opinion (I have seen debug call stack) this happens because actual User entity is wrapper with Achilles proxy which react to gettter/setter calls. Also when I replace changeName: call to direct getter/setter invocation - user.setName("newName"); user.setVersion(user.getVersion()+1); updating became work.
So why it is happens and is there a way to configure Achilles to react of non getter/setter methods calls?
You have to use the setter methods explicitly.
According to the documentation, it intercepts the setter methods only.
"As a consequence of this design, internal calls inside an entity cannot be intercepted
and will escape dirty check mechanism. It is thus recommended to change state of the
entities using setters"
It is probably a design choice from achilles, and I suggest you raise it as an issue on the issues page, so it may receive some attention from the author.
Before do any actions with user you should get user proxy from info.archinnov.achilles.persistence.PersistenceManager and only after that use setters/getters for modification with 'user' entity.
User user = persistenceManager.getProxy(User.class, UUID.fromString(id));