Transaction doesn't works, saves users with the same email - java

I have to save users with unique emails. I added Transactional on my saveUser method. But when I tested I saw that I can add users with the same email. Here is my code:
#Transactional(isolation = Isolation.SERIALIZABLE)
public User saveUser(String email) {
if (userRepository.findByEmailIgnoreCase(email) != null) {
throw new UserAlreadyExistException(format("User with email %s already exists.", email.toLowerCase()));
}
var user = new User();
user.setEmail(email.toLowerCase());
return userRepository.save(user);
}
UserRepository it is a JpaRepository:
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, String> {
User findByEmailIgnoreCase(String email);
}
Here is my test (SprinBootTest):
#Test
public void shouldNotCreateNewUserWithSameEmail() throws Exception {
String requestPayload = objectMapper.writeValueAsString(UserCreateRequest.builder()
.email(EMAIL1)
.rolesName(Set.of(role.getName())).build());
for (int i=0; i<15; i++) {
mockMvc.perform(MockMvcRequestBuilders.post(MAIN_REQUEST_URL)
.contentType(MediaType.APPLICATION_JSON)
.content(requestPayload))
.andExpect(MockMvcResultMatchers.status().isCreated())
.andExpect(jsonPath("$.id").exists())
.andExpect(jsonPath("$.name.firstName").doesNotExist())
.andExpect(jsonPath("$.name.lastName").doesNotExist())
.andReturn();
var r = new Runnable() {
#Override
public void run() {
try {
mockMvc.perform(MockMvcRequestBuilders.post(MAIN_REQUEST_URL)
.contentType(MediaType.APPLICATION_JSON)
.content(requestPayload))
.andExpect(MockMvcResultMatchers.status().isBadRequest());
} catch (Exception e) {
e.printStackTrace();
}
}
};
new Thread(r).start();
userRepository.deleteAll();
}
}
As you can see I run second user creation in other thread. Test passed when I do 3-5 iterations. But with 10-15 iteration it fails with error:
Exception in thread "Thread-10" java.lang.AssertionError: Status
expected:<400> but was:<201>
I.e. user was created. Also I tried to add synchronized to saveUser method, it doesn't helps. What is wrong? And how I can properly prevent not unique emails?
UPD: In User class:
#Column(unique = true, updatable = false, nullable = false)
private String email;

If you don't mind to use hibernate dependency.
#Table(uniqueConstraints = {#UniqueConstraint(columnNames = "email")})
public class user {
String email;
String displayName;
}

Related

How to properly wrap server response to client

Task:
Make a single wrapper for server responses to the client.
My decision:
Create an ApiResponse object and use it in the controller and services
Am I doing the right thing? Sorry I don't know much about this...
ApiResponce
#Data
public class ApiResponse {
private Date timestamp;
private int status;
private String message;
private Object data;
public ApiResponse(int status, String message, Object data) {
this.timestamp = new Date();
this.status = status;
this.message = message;
this.data = data;
}
Controller
#RestController
#RequestMapping("/api/v1/admin")
#RequiredArgsConstructor
#PreAuthorize("hasAuthority('ADMIN')")
public class Admin {
private final UserService userService;
#PostMapping(value = "/users/add", produces = APPLICATION_JSON_VALUE)
public ResponseEntity<ApiResponse> addUser(#RequestBody User user) {
return ResponseEntity.ok(userService.addUser(user));
}
}
Service
public interface UserService {
ApiResponse addUser(User user);
ApiResponse updateUser(User user);
ApiResponse getUserByEmail(String email);
ApiResponse getUserById(Long id);
}
Implementation method example
#Override
public ApiResponse addUser(User user) {
log.info("Saving new user to the database. Email: {}", user.getEmail());
// Check if the user is already in the database
User useDB = userRepo.findByEmail(user.getEmail());
if (useDB != null) {
return new ApiResponse(200, "A user with this e-mail already exists in the system!", user);
}
try {
user.setPassword(passwordEncoder.encode(user.getPassword()));
userRepo.save(user);
return new ApiResponse(200, "User added successfully", null);
} catch (Exception ex) {
log.error("New user added error! " + ex.getMessage());
return new ApiResponse(403, "New user registration error! " + ex.getMessage(), null);
}
}
Am I moving in the right direction? Or is it a sign of bad code?
Actually what I tried above)
As per my knowledge, you're doing good. Make sure to use BCryptPasswordEncoder in Configuration file in order to make password encrypted.

DELETE and PUT endpoints don't work after implementing Spring Security

I'm currently developing a fullstack web application with a React Frontend and Spring Boot backend. I've implemented Spring security and JWT for authentication, but I can't access my API endpoints (see Controller) ever since. I've managed to access the GET request endpoints, but none of the PUT or DELETE requests seem to work despite logging in on the backend before starting a request.
I've seen that disabling csrf solved the problem in another post, but I've never enabled it anyway, so that wouldn't do the trick for me.
WebSecurityConfig file:
#Configuration
#AllArgsConstructor
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/v*/registration/**")
.permitAll()
.anyRequest()
.authenticated().and()
.formLogin();
}
Controller (REST API)
#RestController
#RequestMapping(path = "/question")
#CrossOrigin("*")
public class QuestionController {
private final QuestionService questionService;
#Autowired
public QuestionController(QuestionService questionService) {
this.questionService = questionService;
}
#CrossOrigin("*")
#GetMapping("/all")
public ResponseEntity<List<Question>> getAllQuestions() {
List<Question> questions = questionService.findAllQuestions();
return new ResponseEntity<>(questions, HttpStatus.OK);
}
#CrossOrigin("*")
#GetMapping("/find/{id}")
public ResponseEntity<Question> getQuestionById(#PathVariable("id") Long id) {
Question question = questionService.findQuestionById(id);
return new ResponseEntity<>(question, HttpStatus.OK);
}
#CrossOrigin("*")
#PostMapping("/add")
public ResponseEntity<Question> addQuestion(#RequestBody Question question) {
Question newQuestion = questionService.addQuestion(question);
return new ResponseEntity<>(newQuestion, HttpStatus.CREATED);
}
#CrossOrigin("*")
#PutMapping("/update/{id}")
public ResponseEntity<Question> updateQuestion(#RequestBody Question question) {
Question updateQuestion = questionService.updateQuestion(question);
return new ResponseEntity<>(updateQuestion, HttpStatus.OK);
}
#CrossOrigin("*")
#DeleteMapping("(/delete/{id}")
public ResponseEntity<Question> deleteQuestion(#PathVariable("id") Long id) {
questionService.deleteQuestion(id);
return new ResponseEntity<>(HttpStatus.OK);
}
}
Example for the GET request endpoint that works:
Example for the DELETE request endpoint that doesn't work:
Edit: This is the Code for implementing UserDetailsService
#Service
#Autowired can be left out by using this annotation.
#AllArgsConstructor
public class BenutzerkontoService implements UserDetailsService {
private final static String USER_NOT_FOUND_MSG = "User with email %s not found";
private final BenutzerkontoRepository benutzerkontoRepository;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
private final ConfirmationTokenService confirmationTokenService;
public List<Benutzerkonto> findAllBenutzerkonto() {
// findAll() returns a list of all user objects
return benutzerkontoRepository.findAll();
}
/**
* This method is responsible for identifying the given email inside the database.
*
* #param email
* #return
* #throws UsernameNotFoundException
*/
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return benutzerkontoRepository.findByEmail(email).orElseThrow(() -> new UsernameNotFoundException(String.format(USER_NOT_FOUND_MSG, email)));
}
/**
* The following function checks, whether the user already exists (by email) and registers the user with an
* encoded password, if the email address does not exist already.
*
* The user also gets a random JSON Web Token assigned
*
* #param benutzerkonto
* #return
*/
public String signUpUser(Benutzerkonto benutzerkonto) {
// Check whether user exists
boolean userExists = benutzerkontoRepository.findByEmail(benutzerkonto.getEmail()).isPresent();
if (userExists) {
throw new IllegalStateException("Email is already taken");
}
// Encode the user password
String encodedPassword = bCryptPasswordEncoder.encode(benutzerkonto.getPassword());
// Replace the plain text password with the encoded version
benutzerkonto.setPasswort(encodedPassword);
// Save user to database
benutzerkontoRepository.save(benutzerkonto);
// Create random String via the UUID class for using it as token
String token = UUID.randomUUID().toString();
// Instantiate ConfirmationToken class, which defines the token for account confirmation
ConfirmationToken confirmationToken = new ConfirmationToken(
token,
LocalDateTime.now(),
// Make token invalid after 15 minutes
LocalDateTime.now().plusMinutes(15),
benutzerkonto
);
// Save token to database
// TODO: Shouldn't it be saved by a confirmationTokenRepository object? Why does this also work?
confirmationTokenService.saveConfirmationToken(confirmationToken);
return token;
}
/**
* This function takes the email address as a parameter and enables/activates the email for logging in.
*
* #param email
* #return
*/
public int enableAppUser(String email) {
return benutzerkontoRepository.enableAppUser(email);
}
/**
* This method adds a new user account to the database, but it searches for the passed value of email
* inside the database first. The user object "benutzerkonto" will only be saved in the database repository,
* if the email does not exist already.
*
* #param benutzerkonto
*/
public void addNewUser(Benutzerkonto benutzerkonto) {
// userEmailPresence can be null, if the email does not exist in the database yet, which is why it's an Optional.
Optional<Benutzerkonto> userEmailPresence = benutzerkontoRepository.findBenutzerkontoByEmail(benutzerkonto.getUsername());
if (userEmailPresence.isPresent()) {
throw new IllegalStateException("Email already taken.");
} else {
benutzerkontoRepository.save(benutzerkonto);
}
}
}
Edit2: This is the user class
#Getter
#Setter
#EqualsAndHashCode
#NoArgsConstructor
#Entity
#Table
public class Benutzerkonto implements Serializable, UserDetails {
#SequenceGenerator(
name = "student_sequence",
sequenceName = "student_sequence",
allocationSize = 1
)
#Id
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "student_sequence"
)
#Column(nullable = false)
private Long id;
private String email;
private String passwort;
#Column(nullable = false, updatable = false)
#Enumerated(EnumType.STRING)
private UserRole rolle;
private Boolean locked = false;
// false by default, because user has to confirm via email first
private Boolean enabled = false;
// Constructor
public Benutzerkonto(String email, String passwort, UserRole rolle) {
this.email = email;
this.passwort = passwort;
this.rolle = rolle;
}
#Override
public String toString() {
return "Benutzerkonto{" +
"id=" + id +
", email='" + email + '\'' +
", passwort='" + passwort + '\'' +
", rolle=" + rolle +
", locked=" + locked +
", enabled=" + enabled +
'}';
}
// Methods of UserDetails interface
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(rolle.name());
return Collections.singletonList(authority);
}
#Override
public String getPassword() {
return passwort;
}
#Override
public String getUsername() {
return email;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return !locked;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return enabled;
}
}
So apart from requests coming to /api/v*/registration/** others are secured. What does that mean?, it means until you have authorized users having authorized roles cannot access any other endpoint. So you need to do some things like:
implement UserDetails of package org.springframework.security.core.userdetails and implement the method:
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles == null?null:roles.stream().map(m->new SimpleGrantedAuthority(m.getAuthority())).collect(Collectors.toSet());
}
Add roles to your entity class:
#OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.PERSIST)
#JoinTable(
name = "user_role",
joinColumns = #JoinColumn(
name = "user_id",
referencedColumnName = "id"
),
inverseJoinColumns = #JoinColumn(
name = "role_id",
referencedColumnName = "id"
))
private List<Role> roles;
Use those roles in your endpoint:
#PreAuthorize(hasRole('ROLE_role_name'))
#GetMapping(path = EndPoint.PATIENT_HOME, consumes = "application/json", produces = "application/json")
public ResponseEntity<YourDTO> Home(Principal principal) {
return new ResponseEntity<YourDTO>(yourDTO, HttpStatus.OK);
}

Android Room - How can I check if an entity with the same name already exists before inserting?

I'm creating an app using the mvvm pattern with android room, but I've ran into some trouble validating user input. When a user wants to add an ingredient to the app, they are required to enter a name for this ingredient. I want the app to notify the user if the name is already in use. I have tried some stuff using the Transformations.Map() functions but without any success.
I'm fairly new to the mvvm pattern and LiveData, and I've been stuck on this for quite a while now so any advice would be appreciated.
This is the ingredient entity:
#Entity(tableName = "ingredient")
public class BaseIngredient {
#PrimaryKey(autoGenerate = true)
private int id;
private String name;
private String category;
#ColumnInfo(name = "cooking_time")
private int cookingTime;
#Ignore
public BaseIngredient() {
}
public BaseIngredient(int id, #NonNull String name, #NonNull String category, int cookingTime)
throws InvalidValueException {
this.id = id;
setName(name);
setCookingTime(cookingTime);
setCategory(category);
}
public void setName(String name) throws InvalidNameException {
if (name == null || name.isEmpty())
throw new InvalidNameException("Name is empty");
if (!name.matches("[A-z0-9]+( [A-z0-9]+)*"))
throw new InvalidNameException("Name contains invalid tokens");
this.name = name;
}
public void setCategory(String category) throws InvalidCategoryException {
if (category == null || category.isEmpty())
throw new InvalidCategoryException("Category is empty");
if (!category.matches("[A-z0-9]+"))
throw new InvalidCategoryException("Category contains invalid tokens");
this.category = category;
}
public void setCookingTime(int cookingTime) throws InvalidCookingTimeException {
if (cookingTime < 1)
throw new InvalidCookingTimeException("Time must be positive");
this.cookingTime = cookingTime;
}
/* getters */
public boolean isValid() {
return name != null && category != null && cookingTime != 0;
}
This is the IngredientRepository I'm using:
private IngredientDao ingredientDao;
private LiveData<List<BaseIngredient>> ingredients;
public IngredientRepository(Application application) {
LmcfyDatabase database = LmcfyDatabase.getDatabase(application.getApplicationContext());
ingredientDao = database.ingredientDao();
ingredients = ingredientDao.getAllIngredients();
}
public LiveData<List<BaseIngredient>> getAllIngredients() {
return ingredients;
}
public LiveData<List<BaseIngredient>> getIngredientsWithQuery(String query) {
return ingredientDao.getIngredientsWithQuery("%" + query + "%");
}
public void insert(BaseIngredient ingredient) {
LmcfyDatabase.databaseWriteExecutor.execute(() -> {
ingredientDao.insert(ingredient);
});
}
public LiveData<Integer> getIngredientsWithNameCount(String name) {
return ingredientDao.getIngredientsWithNameCount(name);
}
The IngredientDao:
#Insert(onConflict = OnConflictStrategy.IGNORE, entity = BaseIngredient.class)
long insert(BaseIngredient ingredient);
#Delete(entity = BaseIngredient.class)
void delete(BaseIngredient ingredient);
#Query("SELECT * FROM ingredient")
LiveData<List<BaseIngredient>> getAllIngredients();
#Query("SELECT * FROM ingredient WHERE name LIKE :query")
LiveData<List<BaseIngredient>> getIngredientsWithQuery(String query);
#Query("SELECT COUNT(id) FROM ingredient WHERE name LIKE :name")
LiveData<Integer> getIngredientsWithNameCount(String name);
And finally the ViewModel that is used to create an Ingredient
private final IngredientRepository repository;
private final BaseIngredient ingredient;
private final MutableLiveData<String> nameError;
private final MutableLiveData<String> categoryError;
private final MutableLiveData<String> cookingTimeError;
private final MutableLiveData<Boolean> ingredientValidStatus;
public AddIngredientViewModel(#NonNull Application application) {
super(application);
repository = new IngredientRepository(application);
ingredient = new BaseIngredient();
nameError = new MutableLiveData<>();
categoryError = new MutableLiveData<>();
cookingTimeError = new MutableLiveData<>();
ingredientValidStatus = new MutableLiveData<>();
}
public void onNameEntered(String name) {
try {
ingredient.setName(name);
nameError.setValue(null);
} catch (InvalidNameException e) {
nameError.setValue(e.getMessage());
} finally {
updateIngredientValid();
}
}
public void onCategoryEntered(String category) {
try {
ingredient.setCategory(category);
categoryError.setValue(null);
} catch (InvalidCategoryException e) {
categoryError.setValue(e.getMessage());
} finally {
updateIngredientValid();
}
}
public void onCookingTimeEntered(int cookingTime) {
try {
ingredient.setCookingTime(cookingTime);
cookingTimeError.setValue(null);
} catch (InvalidCookingTimeException e) {
cookingTimeError.setValue(e.getMessage());
} finally {
updateIngredientValid();
}
}
private void updateIngredientValid() {
ingredientValidStatus.setValue(ingredient.isValid());
}
public boolean saveIngredient() {
if (ingredient.isValid()) {
Log.d(getClass().getName(), "saveIngredient: Ingredient is valid");
repository.insert(ingredient);
return true;
} else {
Log.d(getClass().getName(), "saveIngredient: Ingredient is invalid");
return false;
}
}
The onXXEntered() functions in the viewmodel are linked to the textViews in the fragment, and the saveIngredient() function is called when a save button is pressed. The XXError LiveData's are used to display errors to the user.
The real problem lies in the fact that LiveData is async, and the user can change their input and click the save button before the LiveData contains the result from the database. If I want the check the input upon saving it, the 'add activity' will have already finished before the check is done.
Again, any help would be very much appreciated.
I had to do something similar in one of my recent projects. What I did was:
Room cannot create columns with SQLite Unique constraint (if it is not the PrimaryKey - which is your case). So don't initialize the database in your app code using Room. Instead, create a database file outside your application. Add a Unique constraint on the 'name' column. Then add the database file in your project under assets folder. (for example, create a subfolder inside assets - 'db_files' - and copy your pre-created database file under this folder)
I guess you use Singleton pattern for your #DataBase class. Replace your 'getInstance()' method with following:
public static MyDB getInstance(final Context context) {
if(INSTANCE == null) {
synchronized (AVListDB.class) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
MyDB.class,"myDB.db")
.createFromAsset( "db/myDB.db")
.build();
}
}
return INSTANCE;
}
This creates a copy of your pre-packaged database file under applications database files path.
With unique constraint in place, your #Insert and #Update annotated methods will respect the constraint condition, and will throw a SQLiteConstraintException if you try to insert a previously used name. You can catch this exception, and pass it to your View or ViewModel as you wish (I implemented a simple centralized event publisher component).
I hope that helps.

Is this a good practise to check for DUPLICATE value with spring-boot, CrudRepository, MySQL

I have this repository:
public interface AccountsRepository extends CrudRepository<Account, Long> {
}
Then I have this service class:
public class AccountService {
private AccountsRepository accountsRepository;
public AccountService(AccountsRepository accountsRepository) {
this.accountsRepository = accountsRepository;
}
public Account createNewAccount() {
Account account = new Account();
account = tryStoreAccountElseRepeatWithDifferentIdentifier(account);
return account;
}
private Account tryStoreAccountElseRepeatWithDifferentIdentifier(Account account) {
account.setIdentifier(IdentifierGenerator.generateString(6));
try {
return accountsRepository.save(account);
} catch (DataIntegrityViolationException e) {
return tryStoreAccountElseRepeatWithDifferentIdentifier(account);
}
}
}
Unit test:
public class AccountServiceUnitTests {
AccountService fixture;
AccountsRepository mockAccountRespository;
#Before
public void setup() {
mockAccountRespository = mock(AccountsRepository.class);
fixture = new AccountService(mockAccountRespository);
}
#Test
public void repeatCreateAccountWhenIdentifierIsDuplicateValue() {
Account account = new Account();
account.setId(123L);
account.setIdentifier("ABCDEF");
when(mockAccountRespository.save(any(Account.class)))
.thenThrow(DataIntegrityViolationException.class)
.thenThrow(DataIntegrityViolationException.class)
.thenThrow(DataIntegrityViolationException.class)
.thenReturn(account);
Account newAccount = fixture.createNewAccount();
assertEquals(account, newAccount);
}
}
Entity:
#Entity
#Table(name = "accounts")
public class Account {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "identifier", unique = true)
private String identifier;
#Column(name = "username", unique = true)
private String username;
// getter, setter shortened
}
Thing is, I want to store a new Account into the database. Some columns have index UNIQUE. So when you try to insert data MySQL throws an exception if there is a DUPLICATE value.
Since the save(Entitiy) method does not throw a documented exception I tried what would happen and saw that a DataIntegrityViolationException is thrown in case I try to add duplicate value.
So my idea was basically try recursively to insert a new row till no exception is thrown. See: tryStoreAccountElseRepeatWithDifferentIdentifier
Is this way of checking for duplicate value good practise? Or is there a "built-in" solution I don't know?

Hibernate automatic versioning not working (with Spring)

I am trying to use the automatic versioning of Hibernate but when the update method f of the Session is called I do not see the version field in the where clause of the query nor is the version incremented in the database. I am doing something fundamentally wrong probably, but what? Is calling getCurrentSession of sesssionFactory an issue?
I have the following entity class:
package dslibweb.model;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Version;
#Entity
#Table(name = "dsXCT_Recalls")
public class DsXCT_Recalls {
#Id
public String recallId;
public int version;
public String status;
//...... more properties.....
#Version
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
public String getRecallId() {
return recallId;
}
public void setRecallId(String recallId) {
this.recallId = recallId;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}
My controller:
package recalls.controller;
#Controller
public class RecallsDataController {
#Autowired
RecallsService recallsManager;
#Autowired
AuthenticationService authService;
private static final Logger logger = Logger.getLogger(RecallsDataController.class);
private static final String SAVE_RECALLS = "MODIFY XCT RECALLS";
RecallsGrid recalls;
#RequestMapping(value = "/showRecallsGrid")
#ResponseBody
public RecallsGrid showRecallsGrid( HttpSession session, HttpServletResponse response) {
recalls = recallsManager.getRecallsDataGrid((String) session.getAttribute("socketToken"), new GridFilters(0, 0, "", "", "", "", ""));
if (recalls.getError() == null || recalls.getError().equals("")) { // no error
return recalls;
} else {
try {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, recalls.getError());
} catch (IOException e) {
e.printStackTrace();
}
return recalls;
}
}
#RequestMapping(value = "/saveRecalls" , method= RequestMethod.POST)
#ResponseBody
public String saveRecalls( HttpSession session, #RequestParam(value="ids[]", required = false) String [] ids, #RequestParam(value="statuses[]", required = false) String [] statuses){
boolean result = authService.validateUserAction((String) session.getAttribute("socketToken"), SAVE_RECALLS);
if(result)
return recallsManager.saveRecalls(ids, statuses, recalls);
else
return "You do not have authority to perform this action.";
}
}
Where I retrieve a collection of DsXCT_Recalls and show them to the user. The collection is stored in the controller. The user then changes status in one or more entities and I call the saveRecalls method of the recallManager which creates a list of only the changed entities (comparing with the collection stored in the controller).
The recallsManager (service layer) is:
package recalls.service.defaultimpl;
#Service("recallManager")
public class HibernateRecallsDataService implements RecallsService {
#Autowired
JsonRpcRequest jsonReq;
#Autowired
JsonRpcSocketWriterReader socketWriterReader;
#Autowired
JsonRpcRequestConstructor reqConstructor;
#Autowired
RecallsDao hibernateRecallsDao;
private static final Logger logger = Logger.getLogger(HibernateRecallsDataService.class);
#Transactional
public RecallsGrid getRecallsDataGrid(String socketToken, GridFilters filters) {
List<DsXCT_Recalls> recalls = hibernateRecallsDao.findRangeOfRecordsFiltered(filters);
return new RecallsGrid(recalls);
}
#Transactional()
public String saveRecalls(String[] ids, String[] statuses, RecallsGrid recalls) {
List<DsXCT_Recalls> recallList = recalls.getRecalls();
List<DsXCT_Recalls> updatedRecallList = new ArrayList<DsXCT_Recalls>();
for (int i = 0; i < ids.length; i++) {
for (DsXCT_Recalls recall : recallList) {
if (recall.recallId.equals(ids[i])) { // recall is found in the list
if (!statuses[i].equals(recall.getStatus())) { // status has changed
recall.setStatus(statuses[i]);
updatedRecallList.add(recall);
}
}
}
}
return hibernateRecallsDao.saveAll(updatedRecallList);
}
}
The saveAll method of my DAO calls one update method of hibernate session by entity changed:
package recalls.dao.hibernate;
#Repository
public class HibernateRecallsDao implements RecallsDao {
#Autowired(required = true)
#Resource(name = "mySessionFactory")
private SessionFactory sessionFactory;
#SuppressWarnings("unchecked")
public List<DsXCT_Recalls> findRangeOfRecordsFiltered(GridFilters filters) {
return sessionFactory.getCurrentSession().createQuery("from DsXCT_Recalls r WHERE SID = 0 ORDER BY Org, Bank, BIC, SetlDate").list();
}
public String saveAll(List<DsXCT_Recalls> recallList){
int count = 0;
for(DsXCT_Recalls recall:recallList){
sessionFactory.getCurrentSession().update(recall);
count++;
}
return count + " recalls were modified.";
}
}
So apparently the #Version must be above the attribute declaration and not above the getter method.. I am sure I saw this somewhere though. So much time wasted :(

Categories