EclipseLink static weaving? With Gradle? Without a plugin? Yeah, it’s possible. Gradle has a wide range of capabilities to run custom tasks and you can absolutely do EclipseLink static weaving with Gradle without issues.
One major objective in addition to the static weaving is to utilize Gradle’s up-to-date check to make sure tasks are only executed when needed. I’ll show you the solution to that as well.
Example project
Let’s create an example project that’ll showcase what we’re trying to achieve. Go to start.spring.io and generate a project with JPA and H2 support.
Open the project in IntelliJ and right away, let’s edit the build.gradle
. We’ll change the dependencies section a little bit.
dependencies { implementation('org.springframework.boot:spring-boot-starter-data-jpa') { exclude group: 'org.hibernate' } implementation 'org.eclipse.persistence:org.eclipse.persistence.jpa:2.7.10' runtimeOnly 'com.h2database:h2' testImplementation 'org.springframework.boot:spring-boot-starter-test' }
Exclude Hibernate that’s coming with the Spring Boot Starter Data JPA module and instead add EclipseLink. I’m using 2.7.10 for the example since that’s the latest version Spring claims compatibility with according to the docs.
“Developed and tested against EclipseLink 2.7; backwards-compatible with EclipseLink 2.5 and 2.6 at runtime.”
Let’s create a new Actor
entity just for the sake of testing.
@Entity @Table(name = "actors") public class Actor { @Id private UUID id; @Column(name = "first_name") private String firstName; public Actor() { } public Actor(UUID id, String firstName) { this.id = id; this.firstName = firstName; } public UUID getId() { return id; } public String getFirstName() { return firstName; } }
Now, let’s configure EclipseLink for Spring Boot. Create a new JpaConfig
class that we’ll use as the whole configuration for EclipseLink. It has to extend the JpaBaseConfiguration
Spring class.
@Configuration public class JpaConfig extends JpaBaseConfiguration { public JpaConfig(DataSource dataSource, JpaProperties properties, ObjectProvider<JtaTransactionManager> jtaTransactionManager) { super(dataSource, properties, jtaTransactionManager); } @Override protected AbstractJpaVendorAdapter createJpaVendorAdapter() { return new EclipseLinkJpaVendorAdapter(); } @Override protected Map<String, Object> getVendorProperties() { return Map.of( PersistenceUnitProperties.WEAVING, "false", PersistenceUnitProperties.DDL_GENERATION, "drop-and-create-tables" ); } }
This config will register the EclipseLinkJpaVendorAdapter
which covers the specifics to EclipseLink and configures the EclipseLink relevant properties:
PersistenceUnitProperties.WEAVING
– whether or not to enable weaving and which type. Right now it’s set to false, i.e. no weaving will be done for now. Static weaving will come soon enough.PersistenceUnitProperties.DDL_GENERATION
– DDL generation strategy. The drop-and-create strategy will recreate the table structure.
Next, let’s create a new Actor in the database upon startup. I’ll use the CommandLineRunner
Spring interface to execute some code upon application start.
@SpringBootApplication public class EclipselinkStaticWeavingGradleApplication implements CommandLineRunner { public static void main(String[] args) { SpringApplication.run(EclipselinkStaticWeavingGradleApplication.class, args); } public static final UUID ACTOR_ID = UUID.fromString("a6728ebd-e1be-4ba7-9738-cc45b8f0df43"); @PersistenceContext private EntityManager entityManager; @Override @Transactional public void run(String... args) throws Exception { Actor actor = new Actor(ACTOR_ID, "Test"); entityManager.persist(actor); } }
It will persist an Actor in the database with a fixed ID.
Configuring static weaving
Next up, let’s add a test to verify that EclipseLink’s static weaving has happened.
@SpringBootTest class EclipselinkStaticWeavingGradleApplicationTests { @PersistenceContext private EntityManager entityManager; @Transactional @Test public void testStaticWeavingWorks() { Actor actor = entityManager.find(Actor.class, EclipselinkStaticWeavingGradleApplication.ACTOR_ID); assertTrue(actor instanceof PersistenceWeaved, "Actor entity is not weaved by EclipseLink"); } }
This will look up the Actor we saved in the DB and verify that the Actor object implements the interface PersistenceWeaved
. This interface is just a marker interface added by EclipseLink as it’s specified by it’s doc.
“Marker interface used to identify classes modified by the EclipseLink weaver.”
If we run it, it’s going to fail. That’s the expected behavior since static weaving is disabled, for now at least.
Let’s enable it and adjust the compilation process.
Open the JpaConfig
class and switch the PersistenceUnitProperties.WEAVING
property to static
.
@Configuration public class JpaConfig extends JpaBaseConfiguration { public JpaConfig(DataSource dataSource, JpaProperties properties, ObjectProvider<JtaTransactionManager> jtaTransactionManager) { super(dataSource, properties, jtaTransactionManager); } @Override protected AbstractJpaVendorAdapter createJpaVendorAdapter() { return new EclipseLinkJpaVendorAdapter(); } @Override protected Map<String, Object> getVendorProperties() { return Map.of( PersistenceUnitProperties.WEAVING, "static", PersistenceUnitProperties.DDL_GENERATION, "drop-and-create-tables" ); } }
Next up, for the weaving to happen, we’ll need a persistence.xml
file. Let’s create one under the src/main/resources/META-INF
folder – you have to create the META-INF
folder. The content is the following:
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"> <persistence-unit name="example-pu" transaction-type="RESOURCE_LOCAL"> <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider> <exclude-unlisted-classes>false</exclude-unlisted-classes> <properties> <property name="eclipselink.weaving" value="static" /> </properties> </persistence-unit> </persistence>
This file will be used ONLY for the static weaving, nothing else. It won’t be picked up by the application runtime.
Now, go to the build.gradle
and we’ll do a couple of things to execute the static weaving.
There are going to be 3 steps for the weaving to happen, each implemented as individual tasks, at least that’s the very obvious implementation:
- Copy the
persistence.xml
file to the right place, next to the source classes - Do the static weaving
- Delete the persistence.xml from the classes folder, otherwise the classpath will contain 2
persistence.xml
s
Step 1, copying the persistence.xml
file to the classes folder:
task copyPersistenceXml(type: Copy) { def mainSS = sourceSets.main def source = mainSS.java.classesDirectory.get() from "src/main/resources/META-INF/" into "${source}/META-INF/" }
Step 2, doing the static weaving:
task staticWeave(type: JavaExec, dependsOn: [processResources, copyPersistenceXml]) { def mainSS = sourceSets.main def source = mainSS.java.classesDirectory.get() description = 'Performs EclipseLink static weaving of entity classes' def target = source main 'org.eclipse.persistence.tools.weaving.jpa.StaticWeave' args '-persistenceinfo', source, '-classpath', configurations.runtimeClasspath, source, target classpath configurations.runtimeClasspath }
Step 3, removing the previously copied persistence.xml
:
task deletePersistenceXml(type: Delete, dependsOn: staticWeave) { def mainSS = sourceSets.main def source = mainSS.java.classesDirectory.get() delete "${source}/META-INF/" }
And then, last but not least, let’s wire this into the compilation process:
classes.dependsOn staticWeave, deletePersistenceXml
Awesome.
If we run the same test-case again. now it will pass. That indicates static weaving is set up correctly and happens during build time.
Gradle up-to-date check
Despite the fact that the above solution works, it breaks Gradle’s up-to-date check; meaning that even though you don’t change anything between multiple Gradle builds – like when you develop and restart the app/test/whatever – it will recompile the whole thing. The reason behind that comes down to the fact that we are overriding the original compileJava
task’s output with the woven class files, hence Gradle will say it’s not up-to-date.
The solution is to wire the static weaving into the compileJava
task’s output so the up-to-date check will be done on the woven classes, not on the originally compiled classes.
Let’s get rid of the 3 custom tasks in build.gradle
we’ve created so far and replace them with this:
compileJava.doLast { def mainSS = sourceSets.main def source = mainSS.java.classesDirectory.get() copy { from "src/main/resources/META-INF/" into "${source}/META-INF/" } javaexec { description = 'Performs EclipseLink static weaving of entity classes' def target = source main 'org.eclipse.persistence.tools.weaving.jpa.StaticWeave' args '-persistenceinfo', source, '-classpath', configurations.runtimeClasspath, source, target classpath configurations.runtimeClasspath } delete { delete "${source}/META-INF/" } }
This will essentially do the 3 tasks in the same order but it will do it as the last step of the compileJava
task, meaning that the up-to-date check will be done on the woven classes.
Rerun the test, should be green and we’re good. The next time you try to run the test/app/whatever without changing a single line will finally NOT recompile the project.
Summary
It’s not that complicated to do static weaving with EclipseLink in Gradle but definitely takes some time to figure this out. Also, make sure to not break the up-to-date check otherwise your Gradle build might take considerably longer than it should when developing.
The GitHub repo with the example project is here.
Cheers.
What version of Gradle did you use for this project? I keep getting an error on the second line of this code:
1. def mainSS = sourceSets.main
2. def source = mainSS.java.classesDirectory.get()
I’m using Gradle 5.4.1