I'm evaluating spring-data-rest and am running into a situation where the magic no longer appears to be working in my favor.
Say I have a collection of items.
Parent - 1:M - Child
Parent
Long id
String foo
String bar
#OneToMany(...)
#JoinColumn(name = "parent_id", referencedColumnName = "id", nullable = false)
Collection<Child> items
setItems(items) {
this.items.clear();
this.items.addAll(items);
}
#Table(name = "items", uniqueConstraints = {#UniqueConstraint(columnNames = {"parent_id", "ordinal"})})
Child
Long id
String foo
Integer ordinal
The database has a constraint that children of the same parent can't have conflicting values in one particular field, 'ordinal'.
I want to PATCH to the parent entity, overwriting the collection of children. The problem comes with the default behavior of hibernate. Hibernate doesn't flush the changes from when the collection is cleared until after the new items are added. This violates the constraint, even though the eventual state will not.
Cannot insert duplicate key row in object 'schema.parent_items' with unique index 'ix_parent_items_id_ordinal'
I have tried mapping this constraint to the child entity by using #UniqueConstraints(), but this doesn't appear to change the behavior.
I am currently working around this by manually looking at the current items and updating the ones that would cause the constraint violation with the new values.
Am I missing something? This seems like a fairly common use case, but maybe I'm trying too hard to shoe-horn hibernate into a legacy database design. I'd love to be able to make things work against our current data without having to modify the schema.
I see that I can write a custom controller and service, à la https://github.com/olivergierke/spring-restbucks, and this would let me handle the entityManager and flush in between. The problem I see going that way is that it seems that I lose the entire benefit of using spring-data-rest in the first place, which solves 99% of my problems with almost no code. Is there somewhere that I can shim in a custom handler for this operation without rewriting all the other operations I get for free?
In order to customize Spring Data REST (my way to do, I have to speak about with Spring Data REST guys) like following:
Consider we have a exposed repository UserRepository on /users/, you should have at least the following API:
...
/users/{id} GET
/users/{id} DELETE
...
Now you want to override /users/{id} DELETE but keep other API to be handle by Spring Data REST.
The natural approach (again in my opinion) is to write your own UserController (and your custom UserService) like following:
#RestController
#RequestMapping("/users")
public class UserController {
#Inject
private UserService userService;
#ResponseStatus(value = HttpStatus.NO_CONTENT)
#RequestMapping(method = RequestMethod.DELETE, value = "/{user}")
public void delete(#Valid #PathVariable("user") User user) {
if (!user.isActive()) {
throw new UserNotFoundException(user);
}
user.setActive(false);
userService.save(user);
}
}
But by doing this, the following mapping /users will now be handle by org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping instead of org.springframework.data.rest.webmvc.RepositoryRestHandlerMapping.
And if you pay attention on method handleNoMatch of org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping (parent of org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping) you can see the following thing:
else if (patternAndMethodMatches.isEmpty() && !allowedMethods.isEmpty()) {
throw new HttpRequestMethodNotSupportedException(request.getMethod(), allowedMethods);
}
patternAndMethodMatches.isEmpty(): return TRUE if url and method (GET, POST, ...) does not match.
So if you are asking for /users/{id} GET it will be TRUE because GET only exists on Spring Data REST exposed repository controller.
!allowedMethods.isEmpty(): return TRUE if at least 1 method GET, POST or something else matches for the given url.
And again it's true for /users/{id} GET because /users/{id} DELETE exists.
So Spring will throw an HttpRequestMethodNotSupportedException.
In order to by-pass this problem I created my own HandlerMapping with the following logic:
The HandlerMapping has a list of HandlerMapping (here RequestMappingInfoHandlerMapping and RepositoryRestHandlerMapping)
The HandlerMapping loops over this list and delegate the request. If an exception occurs we keep it (we keep only the first exception in fact) and we continues to the other handler. At the end if all handlers of the list throw an exception we rethrow the first exception (previously keeped).
Moreover we implements org.springframework.core.Ordered in order to place the handler before org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.
import org.springframework.core.Ordered;
import org.springframework.util.Assert;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerMapping;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
* #author Thibaud Lepretre
*/
public class OrderedOverridingHandlerMapping implements HandlerMapping, Ordered {
private List<HandlerMapping> handlers;
public OrderedOverridingHandlerMapping(List<HandlerMapping> handlers) {
Assert.notNull(handlers);
this.handlers = handlers;
}
#Override
public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Exception firstException = null;
for (HandlerMapping handler : handlers) {
try {
return handler.getHandler(request);
} catch (Exception e) {
if (firstException == null) {
firstException = e;
}
}
}
if (firstException != null) {
throw firstException;
}
return null;
}
#Override
public int getOrder() {
return -1;
}
}
Now let's create our bean
#Inject
#Bean
#ConditionalOnWebApplication
public HandlerMapping orderedOverridingHandlerMapping(HandlerMapping requestMappingHandlerMapping,
HandlerMapping repositoryExporterHandlerMapping) {
List<HandlerMapping> handlers = Arrays.asList(requestMappingHandlerMapping, repositoryExporterHandlerMapping);
return new OrderedOverridingHandlerMapping(handlers);
}
Et voilà.
Related
I have a Spring project with spring-data-rest as a dependency. I have quite a number of repositories in my project, which spring-data-rest automatically created REST API endpoints for. This suited my needs pretty well until now. Now I have a requirement to change the default functionality of one endpoint for all my repositories, specifically, /BASE_PATH/REPOSITORY. This path responds with a paged list of all records of my db.
Now I want to reimplement this endpoint for all my repositories. This is where I am hitting a roadblock. I tried
#RestController
public class MyTableResource {
private MyTableService myTableService;
#Autowired
public MyTableResource(MyTableService myTableService) {
this.myTableService = myTableService;
}
#GetMapping(value = "/api/v1/myTables", produces = MediaTypes.HAL_JSON_VALUE)
public ResponseEntity getMyTables(#QuerydslPredicate(root = MyTable.class) Predicate predicate) throws NoSuchMethodException {
// My custom implementation
}
}
Now this somewhat works but the problem is I need to write almost the same code for all my repositories. I tried #GetMapping(value = "/api/v1/{repository}", produces = MediaTypes.HAL_JSON_VALUE) but this is also matching /api/v1/notarepository which I have implemented separately.
Also, even if I do #GetMapping(value = "/api/v1/{repository}", produces = MediaTypes.HAL_JSON_VALUE) I would like to get a handle to a repository object (MyTable) using {repository} path variable, which would be myTables in this case.
In short, I want to write a single custom controller for all my repositories, since the logic would be the same for each of them, while making sure the correct repository is called based on the path called also making sure that any path variables I introduce does not hide other controller classes I have written.
More things I have tried
I was attempting to get paged HATEOAS resource objects automatically from my list of entities. For this I found that I can use PagedResourceAssembler
#RestController
public class MyTableResource {
private MyTableService myTableService;
#Autowired
public MyTableResource(MyTableService myTableService) {
this.myTableService = myTableService;
}
#GetMapping(value = "/api/v1/myTables", produces = MediaTypes.HAL_JSON_VALUE)
public ResponseEntity getMyTables(#QuerydslPredicate(root = MyTable.class) Predicate predicate, PagedResourcesAssembler<Object> pagedResourcesAssembler) throws NoSuchMethodException {
// My custom implementation
return ResponseEntity.ok(pagedResourcesAssembler.toResource(myTableList);
}
}
This gives me a good response with the required links for the page but does not give links per entity. Then I found I can hook up PersistentEntityResourceAssembler and pass it to toResource above so I did
#RestController
public class MyTableResource {
private MyTableService myTableService;
#Autowired
public MyTableResource(MyTableService myTableService) {
this.myTableService = myTableService;
}
#GetMapping(value = "/api/v1/myTables", produces = MediaTypes.HAL_JSON_VALUE)
public ResponseEntity getMyTables(#QuerydslPredicate(root = MyTable.class) Predicate predicate, PagedResourcesAssembler<Object> pagedResourcesAssembler, PersistentEntityResourceAssembler assembler) throws NoSuchMethodException {
// My custom implementation
return ResponseEntity.ok(pagedResourcesAssembler.toResource(myTableList, assembler);
}
}
This does not work as reported in How to have PersistentEntityResourceAssembler injected into request methods of custom #RepositoryRestController in a #WebMvcTest unit test .
It kind of works if I replace #RestController with RepositoryRestController but then Predicate stops working as mentioned in https://jira.spring.io/browse/DATAREST-838 .
So, I tried using #QuerydslPredicate RootResourceInformation resourceInformation instead of #QuerydslPredicate(root = MyTable.class) Predicate predicate. This also did not work as my controller endpoint does not have /{repository} in it.
Then I tried setting #GetMapping(value = "/{repository}" produces = MediaTypes.HAL_JSON_VALUE). This threw a mapping conflict error.
So I am completely stuck as to what to do next.
You can extend the default behavior provided by Spring Data Rest by extending RepositoryRestMvcConfiguration.
RepositoryRestMvcConfiguration has a DelegatingHandlerMapping bean which holds a list of HandlerMapping. Spring iterates over this list and tries to find a handler for the request. The order of this list is important. The first one gets picked up first for the execution. So if we add a new handler in front of the ones we already have then our HandlerMapping will be called.
You can use whatever logic you want to find the handler for the request. In your case, this would be if the path variable is a repository name.
The following code adds a new handler:
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
import org.springframework.data.rest.webmvc.support.DelegatingHandlerMapping;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerMapping;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
#Configuration
public class CustomRestMvcConfiguration extends RepositoryRestMvcConfiguration {
public CustomRestMvcConfiguration(ApplicationContext context,
ObjectFactory<ConversionService> conversionService) {
super(context, conversionService);
}
#Override public DelegatingHandlerMapping restHandlerMapping() {
DelegatingHandlerMapping delegatingHandlerMapping = super.restHandlerMapping();
List<HandlerMapping> delegates = delegatingHandlerMapping.getDelegates();
delegates.add(0, new HandlerMapping() {
#Override public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//Your custom logic to decide if you should handle the request
//If you don't want to handle the request return null
return null;
}
});
return new DelegatingHandlerMapping(delegates);
}
}
Hope this helps!
Note: RepositoryRestHandlerMapping is the default one you can check it while writing your logic. It might be helpful.
I'm using SpringBoot 2.x with SpringData-JPA accessing the database via a CrudRepository.
Basically, I would like to call the CrudRepository's methods to update or persist the data. In one use case, I would like to delete older entries from the database (for the brevity of this example assume: delete all entries from the table) before I insert a new element.
In case persisting the new element fails for any reason, the delete operation shall be rolled back.
However, the main problem seems to be that new transactions are opened for every method called from the CrudRepository. Even though, a transaction was opened by the method from the calling service. I couldn't get the repository methods to use the existing transaction.
Getting transaction for [org.example.jpatrans.ChairUpdaterService.updateChairs]
Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.deleteWithinGivenTransaction]
Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.deleteWithinGivenTransaction]
I've tried using different Propagation. (REQUIRED, SUPPORTED, MANDATORY) on different methods (service/repository) to no avail.
Changing the methods #Transactional annoation to #Transactional(propagation = Propagation.NESTED) sounded that this would just do that, but didn't help.
JpaDialect does not support savepoints - check your JPA provider's capabilities
Can I achieve the expected behaviour, not using an EntityManager directly?
I also would like to avoid to having to be using native queries as well.
Is there anything I have overlooked?
For demonstration purposes, I've created a very condensed example.
The complete example can be found at https://gitlab.com/cyc1ingsir/stackoverlow_jpa_transactions
Here are the main (even more simplified) details:
First I've got a very simple entity defined:
#Entity
#Table(name = "chair")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Chair {
// Not auto generating the id is on purpose
// for later testing with non unique keys
#Id
private int id;
#Column(name = "legs", nullable = false)
private Integer legs;
}
The connection to the database is made via the CrudRepository:
#Repository
public interface ChairRepository extends CrudRepository<Chair, Integer> {
}
This is being called from another bean (main methods here are updateChairs and doUpdate):
#Slf4j
#Service
#AllArgsConstructor
#Transactional
public class ChairUpdater {
ChairRepository repository;
/*
* Initialize the data store with some
* sample data
*/
public void initializeChairs() {
repository.deleteAll();
Chair chair4 = new Chair(1, 4);
Chair chair3 = new Chair(2, 3);
repository.save(chair4);
repository.save(chair3);
}
public void addChair(int id, Integer legCount) {
repository.save(new Chair(id, legCount));
}
/*
* Expected behaviour:
* when saving a given chair fails ->
* deleting all other is rolled back
*/
#Transactional
public void updateChairs(int id, Integer legCount) {
Chair chair = new Chair(id, legCount);
repository.deleteAll();
repository.save(chair);
}
}
The goal, I want to achieve is demonstrated by these two test cases:
#Slf4j
#RunWith(SpringRunner.class)
#DataJpaTest
#Import(ChairUpdater.class)
public class ChairUpdaterTest {
private static final int COUNT_AFTER_ROLLBACK = 3;
#Autowired
private ChairUpdater updater;
#Autowired
private ChairRepository repository;
#Before
public void setup() {
updater.initializeChairs();
}
#Test
public void positiveTest() throws UpdatingException {
updater.updateChairs(3, 10);
}
#Test
public void testRollingBack() {
// Trying to update with an invalid element
// to force rollback
try {
updater.updateChairs(3, null);
} catch (Exception e) {
LOGGER.info("Rolled back?", e);
}
// Adding a valid element after the rollback
// should succeed
updater.addChair(4, 10);
assertEquals(COUNT_AFTER_ROLLBACK, repository.findAll().spliterator().getExactSizeIfKnown());
}
}
Update:
It seems to work, if the repository is not extended from either CrudRepository or JpaRepository but from a plain Repository, definening all needed methods explicitly. For me, that seems to be a workaround rather than beeing a propper solution.
The question it boils down to seems to be: Is it possible to prevent SimpleJpaRepository from opening new transactions for every (predefined) method used from the repository interface? Or, if that is not possible, how to "force" the transaction manager to reuse the transaction, opened in the service to make a complete rollback possible?
Hi I found this documentation that looks will help you:
https://www.logicbig.com/tutorials/spring-framework/spring-data/transactions.html
Next an example take from the previous web site:
#Configuration
**#ComponentScan
#EnableTransactionManagement**
public class AppConfig {
....
}
Then we can use transactions like this:
#Service
public class MyExampleBean{
**#Transactional**
public void saveChanges() {
**repo.save(..);
repo.deleteById(..);**
.....
}
}
Yes this is possible. First alter the #Transactional annotation so that it includes rollBackFor = Exception.class.
/*
* Expected behaviour:
* when saving a given chair fails ->
* deleting all other is rolled back
*/
#Transactional(rollbackFor = Exception.class)
public void updateChairs(int id, Integer legCount) {
Chair chair = new Chair(id, legCount);
repository.deleteAll();
repository.save(chair);
}
This will cause the transaction to roll back for any exception and not just RuntimeException or Error.
Next you must add enableDefaultTransactions = false to #EnableJpaRepositories and put the annotation on one of your configuration classes if you hadn't already done so.
#Configuration
#EnableJpaRepositories(enableDefaultTransactions = false)
public class MyConfig{
}
This will cause all inherited jpa methods to stop creating a transaction by default whenever they're called. If you want custom jpa methods that you've defined yourself to also use the transaction of the calling service method, then you must make sure that you didn't annotate any of these custom methods with #Transactional. Because that would prompt them to start their own transactions as well.
Once you've done this all of the repository methods should be executed using the service method transaction only. You can test this by creating and using a custom update method that is annotated with #Modifying. For more on testing please see my answer in this SO thread. Spring opens a new transaction for each JpaRepository method that is called within an #Transactional annotated method
Since I want to use ObservableLists in my entities classes within JavaFX, originally I had a problem with the dedicated List implementation and injection over reflection that Hibernate is using by default. Therefore I decided to have my Entity classes annotated with #Access(AccessType.PROPERTY), because I want to enforce that Hibernate uses my getter and setter methods and not reflection.
In a particular class I have a number of List attributes e.g. protected List<CostEstimate> costEstimates;. For each of these lists I have getters and setters which are annotated accordingly. So far so good, that seems to work.
The trouble is that in my UI I don't want to show e.g. all costEstimates that were created over time, but only the last one. So I created a method public CostEstimate getLastCostEstimate() which would return only the last element from the List. This method is annotated with #Transient since there is no matching column in the MySQL database, since it only returns the last element from the related list.
My controller class binds getLastCostEstimate() of the entity to the according UI element.
In the default constructor of my entity class the costEstimates list is initialized with a initial default estimate, such that getLastCostEstimate() should always return a meaningful CostEstimate. In the debugger I can see that this initialization is executed. However, at run time the costEstimates list is empty and I get an IndexOutOfBoundsException. I assume that has to do with the #Transient annotation ?! I wonder whether I have a coding or design issue? I guess my question is: how to model this "give me only the last element from a list" in a JPA entity class (without too much boilerplate code)?
Thank you for your help!
For your convenience please find following some related code snippets:
#Entity
#Inheritance
#Access(AccessType.PROPERTY) // JPA reading and writing attributes through their accessor getter and setter methods
public abstract class UserRequirement extends Requirement implements Serializable {
..
protected List<CostEstimate> costEstimates; // Money needed
..
protected UserRequirement() {
..
costEstimates = FXCollections.observableArrayList();
setLastCostEstimate(new CostEstimate(0));
..
}
..
#OneToMany(mappedBy="parent", orphanRemoval = true, cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
public List<CostEstimate> getCostEstimates() {
return costEstimates;
}
#SuppressWarnings("unused") // Used by JPA
public void setCostEstimates(List<CostEstimate> newCostEstimates) {
if(newCostEstimates != null) {
((ObservableList<CostEstimate>)costEstimates).setAll(newCostEstimates);
} else {
costEstimates.clear();
}
}
#Transient
public CostEstimate getLastCostEstimate() {
return costEstimates.get(costEstimates.size()-1);
}
public void setLastCostEstimate(CostEstimate costEstimate) {
if(costEstimate = null) {
return;
}
costEstimates.add(costEstimate);
}
..
}
I would like to configure and use a Spring 4.1 AsyncUncaughtExceptionHandler. According to the Spring team (see relevant comment here) one will be able to configure an AsyncUncaughtExceptionHandler either by with the <task:annotation-driven> or by implementing AsyncConfigurer as shown here:
#Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler() ;
}
Now my question is as follows: Is there another web-layer annotation similar to #ExceptionHandler that would work like a AsyncUncaughtExceptionHandler?
As stated in the comment, here's an approach I've taken:
It's about async data imports so all classes are called Import...
What I did not do (yet) is the uncaught exception handling, but reading your post made me think about it and it should be straight forward with Spring-AOP wrapping the Importer.process() methods. This will not be global solution but it would be adaptable for a complete application by using a more generalized Result object.
The Controller uses the ImportRequests to get processing (or done) messages. The Importer itself is not removing the results from the map but this is delegated to the controller instead (A user is clicking delete). We also have a #Scheduled task which cleans up done results after 1 hour to ensure there are not left-overs.
So here's part of the code that the Controller is able to get import results during processing:
#Service
public class ImportRequests {
private final Map<User, ImportResult> importRequests = new ConcurrentHashMap<>();
/** Add, remove, get methods for current user omitted */
}
public class ImportResult {
/** The done. */
private Future<Boolean> done;
/** The error messages. */
private List<String> messages = Collections.synchronizedList(new ArrayList<String>());;
}
#Service
public class ImportService {
#Autowired
private ImportRequests importRequests;
#Autowired
private Importer importer;
public ImportResult doImport(final ImportForm importForm) {
ImportResult result = new ImportResult();
importRequests.addImportResultForCurrentUser(result);
/* This is the actual Async call (process) */
result.setDone(importer.process(result));
return result;
}
}
#Service
public class ImporterImpl implements Importer {
/**
* doProcess will import the *big* file and update the result object with the necessary messages
*/
#Async
public Future<Boolean> process(ImportResult result) {
Boolean done = doProcess(result);
return new AsyncResult<Boolean>(done);
}
}
Hope this helps.
Original Text:
One possibility that I have used is the "#ControllerAdvice" on a class scanned by the servletcontext.
You simply create a method with the exception as a parameter and annotate that method with "#ExceptionHandler". You can even have multiple handlers for specific exception types.
The result of these methods are again handled by the DispatcherServlet, so you can render a view the same way as with request mappings.
I have been going through some Spring / AOP tutorials and have somewhat familiarized myself with the related concepts.
Now coming to my requirements, I need to create an Activities Log implementation which will save the activities of a logged-in user in the DB which can range from applying for a service or creating new users in case of Admin users, etc. On invocation of any method having an annotation (say #ActivityLog), this information is to be persisted in the form of actorId, actionComment, actionTime, actedUponId, ... etc.
Now, if I create a POJO class (that maps to a ActivityLog table in the DB) and want to save this data from inside the Advice (preferably using the same transaction as the method, method uses #Transactional annotation), how do I actually populate the variables in this POJO?? I can probably get the actorId from the session object & actionTime can simply be new Date() but how about the dynamic values for actionComment / actedUponId?
Any help will be brilliant! (BTW, I have a requirement to not use Hibernate Interceptors.)
Here is a complete example:
#Aspect
#Component
public class WebMethodAuditor {
protected final Log logger = LogFactory.getLog(getClass());
public static final String DATE_FORMAT_NOW = "yyyy-MM-dd HH:mm:ss";
#Autowired
AuditRecordDAO auditRecordDAO;
#Before("execution(* com.mycontrollers.*.*(..))")
public void beforeWebMethodExecution(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
String methodName = joinPoint.getSignature().getName();
User principal = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Timestamp timestamp = new Timestamp(new java.util.Date().getTime());
// only log those methods called by an end user
if(principal.getUsername() != null) {
for(Object o : args) {
Boolean doInspect = true;
if(o instanceof ServletRequestDataBinder) doInspect = false;
if(o instanceof ExtendedModelMap) doInspect = false;
if(doInspect) {
if(o instanceof BaseForm ) {
// only show form objects
AuditRecord ar = new AuditRecord();
ar.setUsername(principal.getUsername());
ar.setClazz(o.getClass().getCanonicalName());
ar.setMethod(methodName);
ar.setAsString(o.toString());
ar.setAudit_timestamp(timestamp);
auditRecordDAO.save(ar);
}
}
}
}
}
}
If you are looking to get the actionComment and actedUponId from arguments to the annotated method (assuming they're both strings), you can add binding terms to your #Around pointcut like this:
#Around("#annotation(ActivityLog) && args(actionComment,actedUponId)")
public Object logActivity(ProceedingJoinPoint pjp,
String actionComment, String actedUponId) throws Throwable {
// ... get other values from context, etc. ...
// ... write to log ...
pjp.proceed();
}
The args binding in a pointcut can be used in partially-specified mode, in case there are other arguments about that you aren't interested in, and since the aspect is itself a bean, it can be wired into everything else that is going on in the normal way.
Note that if you're mixing declarative transaction management on the same method calls, you've got to get the order of aspects correct. That's done in part by making the aspect bean also implement the Spring Ordered interface, and by controlling the precedence of transactions through the order attribute to <tx:annotation-driven/>. (If that's impossible, you'll be forced to do clever things with direct transaction handling; that's a vastly more painful option to get right…)
You will be getting a reference to the org.aspectj.lang.JoinPoint in your advice.You can get the name of target method being executed with toShortString().You can have a loop-up/property file with the method-name=comments entries.This comments can be populated into the POJO.actionComment.method-name can be set to POJO.actedUponId.
I hope the advice should run within the same transaction, if the data-access method is adviced and the service method uses #Transactional.