ebean-orm / ebean

Ebean ORM
https://ebean.io
Apache License 2.0
1.47k stars 260 forks source link

ENH: Add experimental entity-field-access ... entities with public fields (no getters/setters/accessors required) #2858

Closed rbygrave closed 2 years ago

rbygrave commented 2 years ago

We can write our entities using public fields and not have getters/setters/accessors like:

@Entity
public class Customer {

  @Id
  public long id;

  @Column(length = 100)
  public String name;

  @OneToMany(mappedBy="customer")
  public List<Contact> contacts;

  ...
}
  // just use PUTFIELD and GETFIELD

  var customer = new Customer();
  customer.name = "Fred";
  customer.contacts.add(new Contact(..));

  DB.save(customer);
  var customer = DB.find(Customer.class, 42);

  var id = customer.id;
  var name = customer.name;
  var city = customer.billingAddress.city;

How to enable entity-field-access

This feature is not enabled by default. We need to explicitly turn it on to use it.

  1. Make sure you are using ebean-agent 13.10.0 or greater
  2. Add a src/main/resources/ebean.mf with a entity-field-access entry like:
entity-field-access: true
  1. If we are NOT using query beans we additionally need to specify the entity-packages that contain entity beans like:
entity-packages: org.example.domain
entity-field-access: true

We don't need to do this when using query beans because the querybean-generator automatically adds a META-INF/ebean-generated-info.mf manifest file with the entity-packages entry for us.

What does this feature do?

Background:

Field access enhancement: What the Ebean enhancement does here is it replaces GETFIELD and PUTFIELD with method calls like _ebean_get_name() and _ebean_set_name(String name).

For example:

  customer.name = "Fred";

  // Is turned into
  customer._ebean_set_name("Fred");
  customer.contacts.add(new Contact(..));

  // Is turned into
  customer._ebean_get_contacts().add(new Contact(..));
  var city = customer.billingAddress.city;

  // Is turned into
  var city = customer._ebean_get_billingAddress()._ebean_get_city();

What this means is that although it looks like we are using "field access / GETFIELD / PUTFIELD" etc the enhancement has changed it to use the methods that Ebean enhancement has added and these methods have all the interception support of dirty checking, lazy loading etc.

We have all the same functionality as if we were using getters/setters/accessors.

Another way to look at it

Ebean enhancement replaces GETFIELD/PUTFIELD calls with its own _ebean_get_name() _ebean_set_name(...) methods. Normally, this is restricted to ONLY doing this replacement on the entity beans themselves.

When using entity-field-access this replacement is extended to any application code.

When should I use this feature?

Currently it's experimental but if you have a lot of entity beans and perhaps a lot of those are from and application perspective "read only" and they are generally "read write" for testing purposes - then you'd be motivated to trying out this feature.

Limitations

Today it works by assuming that classes in the entity-packages are in fact entities. If we have classes that are not entities in the entity packages and we also use field access on those classes that will not work [those GETFIELD/PUTFIELD calls will be changed to method calls like _ebean_get_name() but those methods will not exist as the type isn't an entity bean].

How can I see what is going on here?

If we are using IntelliJ Idea and open .class files IntelliJ will decompile them. We will see the GETFIELD/PUTFIELD code is replaced with the methods like _ebean_get_name() when we look at the decompiled classes.

This view of the decompiled classes will not show synthetic methods, and by default methods added by the ebean enhancement are synthetic. To view the methods and code that the ebean enhancement is adding to entity classes we need to turn that off using synthetic: false.

In the src/main/resources/ebean.mf add synthetic: false like:

synthetic: false
entity-field-access: true

Now, if we look at enhanced entity classes via IntelliJ decompiler we will see those methods like _ebean_get_name() etc.

Maven

When using the maven plugin or maven tile then ebean enhancement will be done for main code in the process-classes lifecycle and in process-test-classes for test code.

mvn clean process-test-classes

Performing the above will clean, compile and apply ebean enhancement without running tests. This is a good way to get the enhancement applied to target/classes & target/test-classes ... which we then get IntelliJ to decompile.

Controlling IntelliJ ebean-agent version

If we want to control exactly which ebean-agent jar is used by the IntelliJ plugin we can:

  1. Put a ebean-agent-X.jar into a ~/.ebean directory
  2. Turn off the plugin via IntelliJ - Build - Ebean enhancement
  3. Turn on the plugin via IntelliJ - Build - Ebean enhancement

The turning off/on is the trigger for the IntelliJ plugin to check for a ~/.ebean/ebean-agent-X.jar to use rather than the one it comes with.

rbygrave commented 2 years ago

Updated IntelliJ plugin submitted awaiting approval. Until then its available via direct link: https://plugins.jetbrains.com/plugin/10082-ebean-enhancer/versions/stable/238074