Spring boot - To execute statement sql update (JPA) - spring

I just have started with Spring Boot and I am practicing in base to this example:
http://www.devglan.com/spring-boot/spring-boot-angular-example
The physical deleted is working ok, but I am trying to do a logical deleted and I do not know how to do it.
This is my classes:
UserController
#DeleteMapping(path ={"logical/{id}"})
public User deleteLogical(#PathVariable("id") int id, #RequestBody User user) {
return userService.deleteLogical(id, user);
}
UserService
User deleteLogical(int id, User user);
UserServiceImpl
#Override
// #Query("update user set remove = false where id = ?1")
public User deleteLogical(int id, User user) {
// user.setRemove(false);
return repository.save(user);
}
UserRepository
User save(User user);
This is the SQL statement I want to execute:
UPDATE user SET remove = false WHERE id =?;
How I could do it? Thanks,

I think you dont need to pass User object. Id will be enough.
So your code will be changed
#DeleteMapping(path ={"logical/{id}"})
public User deleteLogical(#PathVariable("id") int id) {
return userService.deleteLogical(id, user);
}
UserService
User deleteLogical(int id);
UserServiceImpl
#Override
public User deleteLogical(int id) {
User user = repository.findById(id);
user.setRemove(true);
return repository.save(user);
}
That's all.

You try to implement "soft delete" so you can try this approach:
User entity:
#Entity
User {
#Id
#GeneratedValue
private Integer id;
//...
private boolean removed; // user is removed if this property is true
//...
}
User service:
public interface UserService {
Optional<Integer> softDelete(int id);
}
#Service
public UserServiceImpl implements UserService (
// Injecting UserRepo
// First variant - read the user, set it removed, then updated it.
#Transactional
Optional<Integer> softDelete1(int id) {
// Using method findById from Spring Boot 2+, for SB1.5+ - use method findOne
return userRepo.findById(id).map(user -> {
user.setRemoved(true);
userRepo.save(user);
return 1;
});
}
// Second variant - update the user directly
#Transactional
Optional<Integer> softDelete2(int id) {
return userRepo.softDelete(id);
}
}
User controller:
#RestController
#RequestMapping("/users")
public class UserController {
// Injecting UserService
#DeleteMapping("/{id}")
public ResponseEntity<?> softDelete(#PathVariable("id") int id) {
return userService.softDelete1(id) // you can use 1 or 2 variants
.map(i -> ResponseEntity.noContent().build()) // on success
.orElse(ResponseEntity.notFound().build()); // if user not found
}
}
User repo:
public interface UserRepo extends JpaRepository<User, Integer>() {
#Modifying(clearAutomatically = true)
#Query("update User u set u.removed = true where u.id = ?1")
int remove(int id);
default Optional<Integer> softDelete(int id) {
return (remove(id) > 0) ? Optional.of(1) : Optional.empty();
}
}
UPDATED
Or, I think, you can just try to simply override the deleteById method of CrudRepository (not tested - please give a feedback):
#Override
#Modifying(clearAutomatically = true)
#Query("update User u set u.removed = true where u.id = ?1")
void deleteById(Integer id);
then use it in your service.

Related

Spring Data Comparison always false

I'm trying to compare two String values, which on the console are identical, but the returned boolean is always false.
I'm talking about the login() method. I am using PostgreSQL.
This is my Service file:
#Service
public class UserService {
private UserRepository userRepository;
#Autowired
public UserService(UserRepository userRepository) {
this.userRepository=userRepository;
}
public List<Useraccount> getUsers() {
List<Useraccount> userList = new ArrayList<>();
userRepository.findAll().forEach(userList::add);
return userList;
}
public boolean login(String username, String password) {
Useraccount user = userRepository.findByUsername(username).orElseThrow(()-> new IllegalStateException("User with Username "+username+" not found"));
System.out.println(user.getUsername()+user.getPassword()+"out");
System.out.println(username+password+"in");
return (user.getUsername()==username);
}
public String userOutput(String username) {
Useraccount user = userRepository.findByUsername(username).orElseThrow(()-> new IllegalStateException("User with Username "+username+" not found"));
return user.getUsername();
}
}
This is my Repository file:
#Repository
public interface UserRepository extends CrudRepository<Useraccount, Long>{
Optional<Useraccount> findByUsername(String username);
}
This is my Controller file:
#RestController
#RequestMapping("/api/v1/user")
#CrossOrigin
public class UserController {
private UserService userService;
#Autowired
public UserController(UserService userService) {
this.userService=userService;
}
#GetMapping
private List<Useraccount> getUsers(){
return userService.getUsers();
}
#GetMapping("/login")
public boolean login(#RequestParam(required = true) String username, #RequestParam(required =
true) String password) {
return userService.login(username, password);
}
#GetMapping(path="{username}")
public String userOutput(#PathVariable("username") String username) {
return userService.userOutput(username);
}
}
This is my Console output:
Hibernate:
select
useraccoun0_.id as id1_1_,
useraccoun0_.password as password2_1_,
useraccoun0_.username as username3_1_
from
useraccount useraccoun0_
where
useraccoun0_.username=?
DeonisosPasswordout
DeonisosPasswordin
As you can see the in and out is identical, but the boolean always returns false for some reason.
Please use equals method comparison on strings if you re trying to compare the content. In simple words, == checks if both objects point to the same memory location whereas .equals() evaluates to the comparison of values in the objects. So, your login method should return below for accurate results.
return (user.getUsername().equals(username);

How to achieve row level authorization in spring-boot?

Assuming I've the following endpoints in spring boot
GET /todo
DELETE /todo/{id}
How can ensure that only entries for the userid are returned and that the user can only update his own todos?
I've a populated Authentication object.
Is there any build in way I can use? Or just make sure to always call findXyzByIdAndUserId where userid is always retrieved from the Principal?
I'm a bit worried about the possibility to forget the check and displaying entries from other users.
My approach to this would be a 3 way implementation: (using jpa & hibernate)
a user request context
a mapped superclass to get your context
a statement inspector to inject your userid
For example:
public final class UserRequestContext {
public static String getUserId() {
// code to retrieve your userid and throw when there is none!
if (userId == null) throw new IllegalStateException("userid null");
return userId;
}
}
#MappedSuperclass
public class UserResolver {
public static final String USER_RESOLVER = "USER_RESOLVER";
#Access(AccessType.PROPERTY)
public String getUserId() {
return UserRequestContext.getUserId();
}
}
#Component
public class UserInspector implements StatementInspector {
#Override
public String inspect(String statement) {
if (statement.contains(UserResolver.USER_RESOLVER)) {
statement = statement.replace(UserResolver.USER_RESOLVER, "userId = '" + UserRequestContext.getUserId() + "'" );
}
return sql;
}
#Bean
public HibernatePropertyCustomizer hibernatePropertyCustomizer() {
return hibernateProperies -> hibernateProperties.put("hibernate.session_factory.statement_inspector",
UserInspector.class.getName());
}
}
So your Entity looks like this:
#Entity
...
#Where(clause = UserResolver.USER_RESOLVER)
public class Todo extends UserResolver {
...
}

QuerySyntaxException with enum

I have a UserAssignmentRole class like this :
#Data
#Entity
public class UserAssignmentRole {
...
#Enumerated(EnumType.STRING)
public Role role;
}
And the Role is enum, it looks like this:
public enum Role{
admin,
member,
pending
}
Now when in my repository I try to query to select all with role of admin, it gives me error:
#Query("select uar from UserAssignmentRole uar where uar.role=Role.admin")
public List<UserAssignmentRole> listAdmin(Long userID, Long assignmentID);
How this can be solved?
Error : org.hibernate.hql.internal.ast.QuerySyntaxException: Invalid path: 'Role.admin'
Full error : https://pastebin.com/tk9r3wDg
It is a strange but intended behaviour of Hibernate since 5.2.x
An enum value is a constant and you're using a non-conventional naming (lowercase)
Take a look at this issue and Vlad Mihalcea's long explanation of the performance penalty.
If you’re using non-conventional Java constants, then you’ll have to set the hibernate.query.conventional_java_constants configuration property to false. This way, Hibernate will fall back to the previous behavior, treating any expression as a possible candidate for a Java constant.
You can try not to write this sql by yourself but with repository create code like this:
#Repository
public interface UserAssignmentRolelRepository extends JpaRepository<UserModel, Long>{
public List<UserAssignmentRole> findByRole(Role role);
}
And then:
#Autowired
UserAssignmentRolelRepository repository ;
public void someMethod(){
List<UserAssignmentRole> userAssignmentRoles = repository.findByRole(Role.admin);
}
UPDATE 1
As it was point out in this answer: non-conventional naming. You can change labels in your enum to uppercase.
public enum Role{
Admin,
Member,
Pending
}
and then:
#Query("select uar from UserAssignmentRole uar where uar.role=com.example.package.Role.Admin")
public List<UserAssignmentRole> listAdmin(Long userID, Long assignmentID);
UPDATE 2
But if you really want to have lowercase in DB.
It requires more code to change. Enum change to:
public enum Role{
Admin("admin"),
Member("member"),
Pending("pending");
private String name;
Role(String name) {
this.name = name;
}
public String getName() { return name; }
public static Role parse(String id) {
Role role = null; // Default
for (Role item : Role.values()) {
if (item.name.equals(id)) {
role = item;
break;
}
}
return role;
}
}
In UserAssignmentRole
// #Enumerated(EnumType.STRING)
#Convert(converter = RoleConverter.class)
private Role role;
And additional class:
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
#Converter(autoApply = true)
public class RoleConverter implements AttributeConverter<Role, String> {
#Override
public String convertToDatabaseColumn(Role role) {
return role.getName();
}
#Override
public Role convertToEntityAttribute(String dbData) {
return Role.parse(dbData);
}
}

How can I use a stored procedure MySQL in Spring Boot?

I'm trying to solve a problem: When user click on delete a record and instead of delete it, this record will change status to 'false'.
I suppose to create stored procedures in MySQL like "getAllUser", "changeStatus" but I'm confusing what I have to do.
This is structure of my project:
-configuration
JpaConfiguration
-controller
RestApiController
-model
User
-Repository
UserRepository
-Service
UserService
UserServiceImpl
Then I expect the index page only get records that have status is 'true'.
Procedure
CREATE DEFINER=`root`#`localhost` PROCEDURE `getAllUsers`()
BEGIN
SELECT * FROM APP_USER where status = 0;
END
RestApiController
#RestController
#RequestMapping("/api")
public class RestApiController {
#Autowired
UserService userService;
#RequestMapping(value = "/user/", method = RequestMethod.GET)
public List<User> getAllUsers(Boolean status){
Boolean statusWhere = org.apache.commons.lang3.BooleanUtils.isNotTrue(false);
List<User> users = userServiceImpl.getAllUsers(statusWhere);
if (users == null){
return new ArrayList<>();
} else {
return users;
}
}
// Change status
#RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)
public ResponseEntity<?>deleteUserById(#PathVariable("id") long id){
logger.info("Delete User with id {}", id);
User currentUser = userService.findById(id);
currentUser.setStatus(true);
return new ResponseEntity<User>(currentUser, HttpStatus.OK);
}
model/User
#Entity
#NamedStoredProcedureQueries({
#NamedStoredProcedureQuery(
name = "getAllUsers",
procedureName = "getAllUsers",
resultClasses = {User.class},
parameters = {
#StoredProcedureParameter(
mode = ParameterMode.IN,
name = "status",
type = Boolean.class)
}
)
})
service/UserServiceImpl
#Component
#Service("userService")
#Transactional
public class UserServiceImpl implements UserService {
#Autowired
private EntityManager entityManager;
#SuppressWarnings("unchecked")
public List<User> getAllUsers(Boolean status){
StoredProcedureQuery storedProcedureQuery = this.entityManager.createNamedStoredProcedureQuery("getAllUsers");
storedProcedureQuery.setParameter("status",status);
storedProcedureQuery.execute();
return storedProcedureQuery.getResultList();
}
public boolean isUserExist(User user) {
return findByName(user.getName()) != null;
}
}

What is the CLI command to view inside of a set data type in redis

I user a CRUDRepository in my spring data redis project to persist a redis hash in my redis cluster. i have rest api written to persist and get thte values of the data. this works fine.
however my entity annotated with RedisHash is being saved as a set / and i am not able to look inside the value using redis cli.
how do i look inside a set data type(without popping) in redis cli
i looked at redis commands page https://redis.io/commands#set
i only get operations which can pop value . i neeed to simply peek
EDIT:
to make things clearer, i am using spring crudrepo to save the user entity into redis data store. the user entity gets saved as a set data type.
when i query back the user details, i can see entire details of the user
{
userName: "somak",
userSurName: "dattta",
age: 23,
zipCode: "ah56h"
}
i essentially want to do the same using redis cli... but all i get is
127.0.0.1:6379> smembers user
1) "somak"
how do i look inside the somak object.
#RestController
#RequestMapping("/immem/core/user")
public class UserController {
#Autowired
private UserRepository userRepository;
#RequestMapping(path = "/save", method = RequestMethod.GET, produces = "application/json")
#ResponseStatus(HttpStatus.OK)
public void saveUserDetails() {
User user = new User();
user.setAge(23);
user.setUserName("somak");
user.setUserSurName("dattta");
user.setZipCode("ah56h");
userRepository.save(user);
}
#RequestMapping(path="/get/{username}", method = RequestMethod.GET, produces = "application/json")
public User getUserDetails(#PathVariable("username") String userName) {
return userRepository.findById(userName).get();
}
}
#Repository
public interface UserRepository extends CrudRepository<User, String>{
}
#RedisHash("user")
public class User {
private #Id String userName;
private #Indexed String userSurName;
private #Indexed int age;
private String zipCode;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserSurName() {
return userSurName;
}
public void setUserSurName(String userSurName) {
this.userSurName = userSurName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getZipCode() {
return zipCode;
}
public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}
}
I don't understant your descr with your problem, but I understand your title.
In redis set, the member is always string type.
I hope you can offer more info about UserRepository.save:
User user = new User();
user.setAge(23);
user.setUserName("somak");
user.setUserSurName("dattta");
user.setZipCode("ah56h");
userRepository.save(user);
And you can check your redis data and check data type when rest api invoked.

Resources