Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why Spring's @Transactional works without proxy?

I got interested in how Spring's @Transactional works internally, but everywhere I read about it there's a concept of proxy. Proxies are supposed to be autowired in place of real bean and "decorate" base method with additional transaction handling methods. The theory is quite clear to me and makes perfect sense so I tried to check how it works in action. I created a Spring Boot application with a basic controller and service layers and marked one method with @Transactional annotation. Service looks like this:

public class TestService implements ITestService {

@PersistenceContext
EntityManager entityManager;

@Transactional
public void doSomething() {
    System.out.println("Service...");
    entityManager.persist(new TestEntity("XYZ"));
}}

Controller calls the service:

public class TestController {

@Autowired
ITestService testService;

@PostMapping("/doSomething")
public ResponseEntity addHero() {
    testService.doSomething();
    System.out.println(Proxy.isProxyClass(testService.getClass()));
    System.out.println(testService);
    return new ResponseEntity(HttpStatus.OK);
}}

The whole thing works, new entity is persisted to the DB but the whole point of my concern is the output:

Service...
false
com.example.demo.TestService@7fb48179

It seems that the service class was injected explicitly instead of proxy class. Not only "isProxy" returns false, but also the class output ("com.example.demo.TestService@7fb48179") suggests its not a proxy.

Could you please help me out with that? Why wasn't the proxy injected, and how does it even work without proxy? Is there any way I can "force" it to be proxied, and if so - why the proxy is not injected by default by Spring ?

There's not much to be added, this is a really simple app. Application properties are nothing fancy either :

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=superSecretPassword
spring.datasource.url=jdbc:mysql://localhost:3306/heroes?serverTimezone=UTC
spring.jpa.hibernate.ddl-auto=create-drop

Thank you in advance!

like image 658
Antyvirek Avatar asked Sep 01 '18 18:09

Antyvirek


People also ask

How does Spring @transactional really work?

So when you annotate a method with @Transactional , Spring dynamically creates a proxy that implements the same interface(s) as the class you're annotating. And when clients make calls into your object, the calls are intercepted and the behaviors injected via the proxy mechanism.

Does @transactional works on private methods?

The answer your question is no - @Transactional will have no effect if used to annotate private methods. The proxy generator will ignore them. When using proxies, you should apply the @Transactional annotation only to methods with public visibility.

What is the purpose of @transactional annotation?

The @Transactional annotation makes use of the attributes rollbackFor or rollbackForClassName to rollback the transactions, and the attributes noRollbackFor or noRollbackForClassName to avoid rollback on listed exceptions. The default rollback behavior in the declarative approach will rollback on runtime exceptions.

Can we use @transactional on interface?

You certainly can place the @Transactional annotation on an interface (or an interface method), but this works only as you would expect it to if you are using interface-based proxies.


1 Answers

Your understanding is correct, but your test is flawed:

When the spring docs say "proxy", they are referring to the pattern, not a particular implementation. Spring supports various strategies for creating proxy objects. One of these is the java.lang.reflect.Proxy you tested for, but by default spring uses a more advanced technique that generates a new class definition at runtime that subclasses the actual implementation class of the service (and overrides all methods to apply transaction advice). You can see this in action by checking testService.getClass(), which will refer to that generated class, or by halting execution in a debugger, and inspecting the fields of targetService.

The reason that toString() refers to the original object is that the proxy implements toString() by delegating to its target object, which uses its class name to build the String.

like image 62
meriton Avatar answered Oct 14 '22 04:10

meriton