Spring data #Transaction is not executed in order - spring

My Service code likes below:
import javax.transaction.Transactional;
#Service
public class UserServiceImpl implements UserService {
#Transactional
#Override
public void changeAuthorities(Long id, ChangeUserAuthoritiesRequest model) throws RecordNotFoundException {
Optional<User> userOptional = userRepository.findById(id);
if (userOptional.isPresent()) {
User user = userOptional.get();
long result = userAuthoritiesRepository.removeByUser(user);
// System.out.println(userAuthoritiesRepository.findByUser(user));
model.getAuthorityIds().stream().forEach(authorityId -> {
UserAuthority userAuthority = new UserAuthority();
Authority authority = authorityRepository.findById(authorityId).get();
userAuthority.setUser(user);
userAuthority.setAuthority(authority);
userAuthoritiesRepository.save(userAuthority);
});
} else {
throw new RecordNotFoundException("User not found with id: " + id);
}
}
}
The code means "delete all records with the given id and then add again the new ones" (the new ones may be the same the old ones).
My problem is method userAuthoritiesRepository.removeByUser(user) is not executed before new records are saved by userAuthoritiesRepository.save(userAuthority). So the app raises the exception:
Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: Violation of UNIQUE KEY constraint 'user_authority_unique'. Cannot insert duplicate key in object 'dbo.user_authorities'. The duplicate key value is (1, 1).
at com.microsoft.sqlserver.jdbc.SQLServerException.makeFromDatabaseError(SQLServerException.java:258)
at com.microsoft.sqlserver.jdbc.SQLServerStatement.getNextResult(SQLServerStatement.java:1535)
at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement.doExecutePreparedStatement(SQLServerPreparedStatement.java:467)
at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement$PrepStmtExecCmd.doExecute(SQLServerPreparedStatement.java:409)
at com.microsoft.sqlserver.jdbc.TDSCommand.execute(IOBuffer.java:7151)
at com.microsoft.sqlserver.jdbc.SQLServerConnection.executeCommand(SQLServerConnection.java:2478)
at com.microsoft.sqlserver.jdbc.SQLServerStatement.executeCommand(SQLServerStatement.java:219)
at com.microsoft.sqlserver.jdbc.SQLServerStatement.executeStatement(SQLServerStatement.java:199)
at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement.executeUpdate(SQLServerPreparedStatement.java:356)
at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61)
at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:175)
... 177 more

There is nothing wrong with the #Transaction annotation or the order of execution of your code. Its just that the changes aren't pushed to the database. So ou are getting that error.
You will have to call the flush() method after the removeByUser() method call to push the changes to the database.
You can check this link for understanding why it is needed

Related

Unable to get modification context for Active Directory records through Spring LDAP

I am trying to use Spring LDAP to retrieve and modify user information in an Active Directory server, but I can't retrieve a user record by dn so that I can modify it.
I am able to find the record by username with the LdapTemplate.search method. There is no dn attribute in the record, but distinguishedName looks like it should be correct. When I use LdapTemplate.lookupContext to retrieve the record by dn, however, the server says that it can't find the record by the dn that it just gave me. What am I doing wrong?
It seem wrong that the LdapTemplate search method doesn't give you a handle that you can use without doing a second query from the Active Directory. Is there a better way to do this?
I have created a sample Groovy application to demonstrate the problem. My Spring Boot application creates this class and then invokes the runTest method.
package edu.sunyjcc.gateway.ldap;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.ldap.LdapName;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.LdapTemplate;
import static org.springframework.ldap.query.LdapQueryBuilder.query;
import org.springframework.ldap.core.DirContextOperations;
public class ActiveDirectoryDNSample {
LdapTemplate ldapTemplate;
/** Attributes to fetch from server */
static attributeList = [
"sAMAccountName",
"distinguishedName",
"baseDn",
"userPrincipalName",
];
/** This will represent the record retrieved from Active Directory */
class Person {
/** Raw data from server */
Map attributes = [:];
/** Return the distinguished name */
String getDn() {
attributes?.distinguishedName;
}
String getUsername() {
attributes?.sAMAccountName;
}
String toString() {
"${this.username} <${this.getDn()}>"
}
/** Get a handle to the object from AD so we can modify it. This fails. */
def getContext() {
assert ldapTemplate;
println "in getContext()";
def dn = new LdapName(this.getDn());
println "...dn=$dn"
assert dn;
// The next line throws an exception.
DirContextOperations context = ldapTemplate.lookupContext(dn);
println "...context=$context"
}
}
/** Convert the attributes from AD into a Person object */
class RecordMapper implements AttributesMapper<Person> {
/** Create a Person object from the attribute map */
Person mapFromAttributes(Attributes attributes)
throws NamingException {
assert ldapTemplate;
Person prec = new Person(
ldapTemplate: ldapTemplate
);
attributeList.collect {
[attrName: it, attr: attributes.get(it)]
}.grep {it.attr}.each {
prec.attributes."${it.attrName}" = it.attr.get() as String;
}
return prec;
}
}
/** Get a user from Active Directory */
public List<Person> getByUsername(String username) throws Exception {
assert ldapTemplate;
AttributesMapper attrMapper = new RecordMapper();
assert attrMapper;
List s = ldapTemplate.search(
query().
where("sAMAccountName").is(username),
attrMapper
);
if (s == null) {
System.err.println("s is null");
}
return s?:[];
}
/** Try to fetch a record and get a modify context for it */
public runTest(String username) {
println "In ActiveDirectoryDNSample.runText($username)"
assert ldapTemplate;
def records = getByUsername(username);
println "Retrieved ${records?.size()} records";
records.each {println " $it"}
println "Now try to get the context for the records"
records.each {
person ->
println " getting context for $person";
def context = person.getContext();
println " context=$context"
}
}
public ActiveDirectoryDNSample(LdapTemplate ldapTemplate ) {
this.ldapTemplate = ldapTemplate;
}
}
In ActiveDirectoryDNSample.runText(testuser)
Retrieved 1 records
testuser <CN=Test User,CN=Users,DC=jccadmin,DC=sunyjcc,DC=edu>
Now try to get the context for the records
getting context for testuser <CN=Test User,CN=Users,DC=jccadmin,DC=sunyjcc,DC=edu>
in getContext()
...dn=CN=Test User,CN=Users,DC=jccadmin,DC=sunyjcc,DC=edu
and then it dies with a javax.naming.NameNotFoundException with the following data.
[LDAP: error code 32 - 0000208D: NameErr: DSID-03100238, problem 2001 (NO_OBJECT), data 0, best match of:
'CN=Users,DC=jccadmin,DC=sunyjcc,DC=edu'
\0]
Thanks for any help you can give me.
It turns out that there was, indeed, a better way. Instead of using an org.springframework.ldap.core.AttributesMapper in the search, you use org.springframework.ldap.core.ContextMapper.
In my example, I added a field to the Person class, which will hold a reference to the context.
DirContextOperations context;
Then I created a new class extending org.springframework.ldap.core.support.AbstractContextMapper.
class PersonContextMapper extends AbstractContextMapper {
#Override
protected Object doMapFromContext(DirContextOperations ctx) {
AttributesMapper attrMapper = new RecordMapper();
Person p = attrMapper.mapFromAttributes(ctx.attributes);
p.context = ctx;
return p;
}
}
When I passed it to the ldapTemplate.search method in the place of the AttributeMapper, I was able to use the context to update the Active Directory.

Order of processing REST API calls

I have a strage(for me) question to ask. I have created synchronized Service which is called by Controller:
#Controller
public class WebAppApiController {
private final WebAppService webApService;
#Autowired
WebAppApiController(WebAppService webApService){
this.webApService= webApService;
}
#Transactional
#PreAuthorize("hasAuthority('ROLE_API')")
#PostMapping(value = "/api/webapp/{projectId}")
public ResponseEntity<Status> getWebApp(#PathVariable(value = "projectId") Long id, #RequestBody WebAppRequestModel req) {
return webApService.processWebAppRequest(id, req);
}
}
Service layer is just checking if there is no duplicate in request and store it in database. Because client which is using this endpoint is making MANY requests continously it happened that before one request was validated agnist duplicate other the same was put in database - that is why I am trying to do synchronized block.
#Service
public class WebAppService {
private final static String UUID_PATTERN_TO = "[a-zA-Z0-9]{8}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{12}";
private final WebAppRepository waRepository;
#Autowired
public WebAppService(WebAppRepository waRepository){
this.waRepository= waRepository;
}
#Transactional(rollbackOn = Exception.class)
public ResponseEntity<Status> processScanWebAppRequest(Long id, WebAppScanModel webAppScanModel){
try{
synchronized (this){
Optional<WebApp> webApp=verifyForDuplicates(webAppScanModel);
if(!webApp.isPresent()){
WebApp webApp=new WebApp(webAppScanModel.getUrl())
webApp=waRepository.save(webApp);
processpropertiesOfWebApp(webApp);
return new ResonseEntity<>(HttpStatus.CREATED);
}
return new ResonseEntity<>(HttpStatus.CONFLICT);
}
} catch (NonUniqueResultException ex){
return new ResponseEntity<>(HttpStatus.PRECONDITION_FAILED);
} catch (IncorrectResultSizeDataAccessException ex){
return new ResponseEntity<>(HttpStatus.PRECONDITION_FAILED);
}
}
}
Optional<WebApp> verifyForDuplicates(WebAppScanModel webAppScanModel){
return waRepository.getWebAppByRegex(webAppScanModel.getUrl().replaceAll(UUID_PATTERN_TO,UUID_PATTERN_TO)+"$");
}
And JPA method:
#Query(value="select * from webapp wa where wa.url ~ :url", nativeQuery = true)
Optional<WebApp> getWebAppByRegex(#Param("url") String url);
processpropertiesOfWebApp method is doing further processing for given webapp which at this point should be unique.
Intended behaviour is:
when client post request contains multiple urls like:
https://testdomain.com/user/7e1c44e4-821b-4d05-bdc3-ebd43dfeae5f
https://testdomain.com/user/d398316e-fd60-45a3-b036-6d55049b44d8
https://testdomain.com/user/c604b551-101f-44c4-9eeb-d9adca2b2fe9
Only first one will be stored within database but at this moment this is not what is happening. Select from my database:
select inserted,url from webapp where url ~ 'https://testdomain.com/users/[a-zA-Z0-9]{8}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{12}$';
2019-11-07 08:53:05 | https://testdomain.com/users/d398316e-fd60-45a3-b036-6d55049b44d8
2019-11-07 08:53:05 | https://testdomain.com/users/d398316e-fd60-45a3-b036-6d55049b44d8
2019-11-07 08:53:05 | https://testdomain.com/users/d398316e-fd60-45a3-b036-6d55049b44d8
(3 rows)
I will try to add unique constraint on url column but I can't imagine this will solve the problem while when UUID changes new url will be unique
Could anyone give me a hint what I am doing wrong?
Question is related with the one I asked before but not found proper solution, so I simplified my method but still no success

Problem in Handling Unit of work using Hibernate JPA

I use Spring + Hibernate + JPA
I need to handle the list of customers by inserting their orders.
Here is the Unit of work :
for(Customer customer: CustomerList) {
List<Order> orderList = customer.getOrders();
for(Order order: OrderList) {
//1. Insert order into ORDER table
//If insert fails due to Duplicate key then no rollback and I follow steps 2 & 3.
//If insert fails due to any reason except duplicate key then rollback all the previous transactions
//2. select the order record (If any issue during selection then rollbackall the previous transactions)
//3. Update the order If status of the order is different from that in DB (If any issue during update then rollback all the previous transactions)
}
// 4. Update Customer record into CUSTOMER table (If any issue during update then rollback all the previous transactions)
}
Commit is required when all the orders and customer db processes are ok.
Insert order
1.a If duplicate order do not roll back. But select that order from table and update if the status of the order is different in the req compared to the one in db
1.b If any other error during inserting of ORDER then roll back
1.c If no error then proceed inserting orders of the particular Customer
Once orders of the particular Customer is done, then update Customer table
Loop continues..
While handling point 1, 2 and 3 If everything is ok, then commit is required.
If any issues in the middle then all transactions are rolled back
Controller --> Facade layer --> Service --> Repository/Dao
Facade:
#Autowired
MyServiceBean serviceBean;
#Transactional(noRollbackFor = {EntityExistsException.class, PersistException.class, ConstraintViolationException.class, DataIntegrityViolationException.class})
#override
public void facadeMethod(MyReq req) {
List<Customer> custList = req.getCustomers():
for(Customer customer: CustList) {
List<Order> orderList = customer.getOrders();
for(Order order: orderList) {
String dbAction = "";
try {
dbAction = serviceBean.insertOrder(order);
} catch(Exception e) {
// log exception and roll back completely
}
if("handleDupl".equalsTo(dbAction) {
serviceBean.handleDuplOrder(order);
}
}
myService.updateCustomer(customer);
}
}
Service:
#Autowired
MyRepository repo;
#Transactional(propagation = propagation.REQUIRES_NEW)
#override
public String inserOrder() {
String dbAction = "";
try {
repo.insertOrderDao(order);
} catch(all duplicate key exceptions like entityExist, persist, ConstraintVioaltion, DataIntegrity e) {
dbAction = "handleDuplOrder";
} catch(all other Exception except duplicate key e) {
// roll back and throw exception to Facade layer
}
return dbAction;
}
#Transactional(propagation = propagation.REQUIRES_NEW)
#override
public void handleDuplOrder(Order order) {
try {
repo.selectOrderDao(order);
repo.updateOrder(order);
} catch(Exception e) {
// roll back and throw exception to Facade layer
}
}
Repository:
#PersistentContext(unitNmae = "MY_SHCEMA")
EntityManager entityManager;
#Override
public void insertOrderDao(Order order) {
entityManager.persist(order);
entityManager.flush();
}
Problem:
When I send req with One customer who has single order, where the order is duplicate, I see PersistException is caught inside Service method and when it exists from Service method it also throws TransactionSystemException(nested exception is RollbackException: Transaction is marked as rollback only, could not commit JPA transaction) is thrown to Facade layer irrespective of the how I suppress the exception in inner transaction.
Kindly advice If I can achieve Unit of work commit or rollback in this way.
Expected:
I want Spring's #Transactional to ignore Duplicate key exceptions by not rolling back and not affecting the next transaction.
Your outer transaction still fail because you throw ApplicationDuplOrderException() inside your service.
You should setup your services like below:
#Transactional
#Override
public void facadeMethod(MyReq req) {
List<Customer> custList = req.getCustomers():
for(Customer customer: CustList) {
List<Order> orderList = customer.getOrders();
for(Order order: orderList) {
try {
myService.insertOrder(order);
} catch(Exception e) {
// log exception and roll back completely
throw e; // important, you must rethrow
}
}
myService.updateCustomer(customer);
}
}
#Transactional(propagation = propagation.REQUIRES_NEW)
#Override
public void inserOrder() {
try {
repo.insertOrderDao(order);
} catch(all duplicate key exceptions like entityExist, persist, ConstraintVioaltion, DataIntegrity e) {
log.error(xxx); // instead of throwing
} catch(all other Exception except duplicate key e) {
throw e;
}
}

#RepositoryEventHandler is not getting invoked

I have created a simple repository for my app users:
#RepositoryRestResource(path = "users")
public interface AppUserRepository extends JpaRepository<AppUserModel, Long> {
List<AppUserModel> findByUsername(#Param("username") String username);
}
However, I need to take care of password encryption before a new user gets inserted. For this I implemented a #RepositoryEventHandler:
#RepositoryEventHandler(AppUserModel.class)
public class AppUserService {
#HandleBeforeSave
public void handleProfileSave(AppUserModel appUserModel) {
System.out.println("Before save ..");
}
#HandleBeforeCreate
public void register(AppUserModel appUserModel) {
System.out.println("Before create ..");
}
}
The problem is that none of those two event handlers are getting executed. As a result AppUserModel is tried to be persisted but fails because no salt (for the password) has been generated and set - that's how I can tell that the JpaRepository<AppUserModel, Long> is working so far.
{
"cause": {
"cause": {
"cause": null,
"message": "ERROR: null value in column "password_salt" violates not-null constraint Detail: Failing row contains (26, 2017-11-18 21:21:47.534, 2017-11-18 21:21:47.534, f, asd, null, haha#gmx.at)."
},
"message": "could not execute statement"
},
"message": "could not execute statement; SQL [n/a]; constraint [password_salt]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement"
}
It looks to me that you just need to create AppUserService bean. The easiest option would be to annotate it with #Component
#Component
#RepositoryEventHandler(AppUserModel.class)
public class AppUserService {
//...
}
Another way could be providing the bean from within another configuration class:
#Configuration
public class RepositoryConfiguration {
#Bean
AppUserEventHandler appUserService() {
return new AppUserEventHandler();
}
}
However, I prefer the solution provided by #varren.

JPA Transaction not commiting at the end of method

I have two methods:
1:
#Transactional
public Long add(Long clientId) {
Contact contact = new Contact();
contact.setClientId(clientId);
contact.setValue('test#test.com');
contact.setType('EMAIL');
contact.setDateStart(new Date());
// autowired repo
return contactRepository.saveAndFlush(contact);
}
2:
#Transactional(propagation = Propagation.NOT_SUPPORTED)
public void emailsValidator(Long clientId) {
Specification<Client> spec = ClientSearchSpecification.getByClientIdWithActiveEmails(clientId);
List<Client> clientList = clientRepository.findAll(spec);
}
SearchSpecification:
public static Specification<Client> getByClientIdWithActiveEmails(Long clientId) {
return (root, query, cb) -> {
query.distinct(true);
Join<Client, Contact> clientContactJoin = (Join) root.fetch(Client_.contactList);
List<Predicate> predicates = new ArrayList<>();
predicates.add(cb.equal(root.get(AbstractEntity_.id), clientId));
predicates.add(cb.equal(clientContaktJoin.get(Contakt_.type), 'EMAIL'));
predicates.add(cb.lessThan(clientContaktJoin.get(Contakt_.dateStart), cb.currentTimestamp()));
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
};
}
Main method:
#RequestMapping(value = "/{clientId}/contact/add", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
public void addContact(#PathVariable(value = "clientId") Long clientId) {
contactService.add(clientId);
errorValidationService.emailsValidator(clientId);
}
My db properties:
hibernate.transaction.auto_close_session: true
hibernate.connection.autocommit: true
org.hibernate.flushMode: COMMIT
The problem - DB doesn't contain new entity from step 1 while executing step 2. The object is commited only after all rest call finishes. How do I commit entity on step 1 so that I can select and join it in step 2?
You don't need to be committed to select your data (within the same transaction).
The problem here is that you've said step 2 needs to be `NOT_SUPPORTED, which implies that step 1 must commit before step 2 can see any of the data it inserted.
Try changing the propagation of step 1 to REQUIRES_NEW to ensure the commit occurs before you get to step 2.
It turned out that I was using two different #Transaction implementations in repository interface:
#Transactional
public interface ClientRepository extends JpaRepository<Client, Long>, JpaSpecificationExecutor<Client>
I used:
import javax.transaction.Transactional;
I should've use:
import org.springframework.transaction.annotation.Transactional;
Which meant that settings for spring #Transaction were not applied and it couldn't commit itself at the end of the method, It did commits only at the end of the call.

Resources