EclipseLink static weaving with Gradle

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.xmls

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.

Follow me on Twitter and Facebook if you wish.

Leave a Reply

Your email address will not be published.