A Ruby lover's experience with Spring Boot - Part 2: Advanced Queries, Extended ORM types, and further debugging

 

Welcome to Part 2 of my adventures through Spring Boot. Thank you for returning to follow along with my journey learning how to transition my Ruby-colored glasses to more Java tinted ones. Next, I’ll be looking at more advanced ORM capabilities to mirror ActiveRecord, database migrations, and how to validate all of these changes.

During my basic wiring of Hibernate ORM, a lot of examples use raw SQL to return results of more complex queries. The point of using an ORM is to get out of writing raw SQL, so I began to search for alternatives. There were many solutions I found but the biggest two were JOOQ and QueryDSL. I elected for QueryDSL as I found the most StackOverflow examples, Spring boot samples, and functional deployed applications. I will note here JOOQ also has a free and paid tier versions of its libraries which I generally avoid if possible, it’s not as OSS friendly as I would prefer in that regard.

Some of the Relevant Tools we’ll be using

I wanted to include a list of tools you’ll see added as well as some existing tooling being used here:

Starting up

To start, we need to update our pom.xml with the libraries we need, so I’ve added the excerpts that are new for this post (not the whole xml file).

<!--    pom.xml of the Application -->
    <properties>
<!--    under the main dependencies section     -->      
        <com.querydsl.version>[4.3,)</com.querydsl.version>
    </properties>
<!--    under the main dependencies section     -->
        <dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-core</artifactId>
            <version>${com.querydsl.version}</version>
        </dependency>
        <dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-apt</artifactId>
            <version>${com.querydsl.version}</version>
        </dependency>
        <dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-jpa</artifactId>
            <version>${com.querydsl.version}</version>
        </dependency>

<!--    under the build section     -->
            <plugin>
                <groupId>com.mysema.maven</groupId>
                <artifactId>apt-maven-plugin</artifactId>
                <version>1.1.3</version>
                <executions>
                    <execution>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>process</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/generated-sources</outputDirectory>
                            <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
                        </configuration>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>com.querydsl</groupId>
                        <artifactId>querydsl-apt</artifactId>
                        <version>${com.querydsl.version}</version>
                    </dependency>
                </dependencies>
            </plugin>

There are a few pieces to have Maven build for you, but the short version here is that QueryDSL will auto-generate much of the functionality with our JpaRepositories, enabling us to write a lot of advanced queries without much boilerplate. You’ll notice differences from some of the sample repositories out there. Note the plugin processor change:

com.querydsl.apt.jpa.JPAAnnotationProcessor

After running a fresh “mvn compile” phase, we can look at the folder under to

target/generated-sources/com/fulfillment/models 

We can see a file called QCustomer.java. This is all the extra boilerplate auto-generated to help query across multiple tables with type safety, but also enhances the whole Spring Boot app, as we’ll see later. For now, we are going to add the Order entity, repository, and add Service classes to handle some of the more complex work ahead. The Order Table will be simple with a slight change, we’re going to add in a JSONB column.

package com.demo.fulfillment.models;

import com.demo.fulfillment.models.subtypes.OrderItems;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.vladmihalcea.hibernate.type.json.JsonBinaryType;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.*;
import org.springframework.format.annotation.DateTimeFormat;

import javax.persistence.*;
import javax.persistence.AccessType;
import javax.persistence.Entity;
import javax.persistence.ForeignKey;
import javax.persistence.Table;
import java.sql.Date;
import java.util.UUID;

@Entity
@Access(AccessType.FIELD)
@Table(name = "orders")
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
@TypeDefs({@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)})
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id", unique = true, nullable = false)
    private UUID id;

    @Column(name = "customer_id")
    private UUID customerId;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "customer_id", insertable = false, updatable = false,
                foreignKey = @ForeignKey(name = "fk_customer_id_order"))
    @Fetch(FetchMode.JOIN)
    @JsonIgnore
    private Customer customer;

    @Type(type = "jsonb")
    @Column(name = "items", columnDefinition = "jsonb")
    private OrderItems items;

    @Column(name = "created_at")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
    @CreationTimestamp
    private Date createdAt;

    @Column(name = "updated_at")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
    @UpdateTimestamp
    private Date updatedAt;
}

# Order Repository
package com.demo.fulfillment.repositories;

import com.demo.fulfillment.models.Customer;
import com.demo.fulfillment.models.QCustomer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.querydsl.binding.QuerydslBinderCustomizer;
import org.springframework.stereotype.Repository;

import java.util.UUID;

@Repository
public interface OrderRepository extends JpaRepository<Order, UUID>,
        QuerydslBinderCustomizer<QOrder>,
        QuerydslPredicateExecutor<Order> {
}

# OrderItem Class
package com.demo.fulfillment.models.subtypes;

import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.Data;

@Data
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class OrderItem {

    private String sku;
    private Integer quantity;
    private Double unitPrice;
}

Where The Magic Sets IN

My Ruby and RoR brain doesn’t like doing extra work so now let’s expose the Orders API controller. But…how exactly do I get a Customer Record AND and Order record paired together in the API? More worrisome, how can I strongly-type my SQL query and have Spring handle as much of that for me as possible?

There are a couple of additional pieces we’ll be needing here before we get to work on the controller. The first, is the OrderService class. Why do you need a @Service class? we just made the ORM accessor in the Repository? There are a few reasons for this but I’ll explain alongside our class. we can implement our OrderService Class as follows:

The way Spring and Java handle transactions, caching, query reuse, and a few other features, means Spring is handling session state in the JVM for the fulfillment application

package com.demo.fulfillment.services;

import com.demo.fulfillment.models.Customer;
import com.demo.fulfillment.models.Order;
import com.demo.fulfillment.repositories.OrderRepository;
import com.querydsl.core.types.Predicate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.util.Pair;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class OrderService {

    @Autowired
    OrderRepository orderRepository;

    public List<Order> searchOrders(Predicate predicate) {
        return (List<Order>) orderRepository.findAll(predicate);
    }

    public List<Pair<Customer, Order>> getEachCustomerFirstOrder() {
        List<Pair<Customer, Order>> firstOrders = null;

        firstOrders = orderRepository.getFirstOrderPerCustomer();
        return firstOrders;
    }
}
  • This means that a transaction may not occur directly on a @Repository function unless annotated with the @Transactional

  • Most Auto-generated code falls under the non-annotated category

  • Any functions added to a @Repository class will have the same restriction

Annotating a class with @Service tells Spring the transaction is complete at that point

  • This gates how long database querying takes as well as how many calls get executed

  • This can be a way to efficiently run all necessary queries for a code path before releasing the connection to other working threads in the JVM

    • Making new connections can be costly in network time, so best to avoid as much as possible

  • Business logic lives at the Service layer, Repository logic should be strictly interacting with the database unless there is A VERY GOOD REASON (my scars of reading legacy code are showing)

 

Magic Explained

Not much here, but as you read through you’ll notice orderRepository.getFirstOrderPerCustomer(); function on the repository.

“Hey you didn’t have that there before, where did that come from?”

The truth is, I added additional functionality using QueryDSL, our OrderRepository, and our auto-generated QCustomer and QOrder classes. You’ll also notice the “Tuple” class being used here. This is how QueryDsl uses Spring JPA, Jackson serialization, and Hibernate ORM together to serialize and deserialize Java objects into database records and vice versa, with strong typing, data validations, and more. Read more about QueryDsl tuple in this link to their documentation.

# OrderCustom.java interface
package com.demo.fulfillment.repositories;

import com.demo.fulfillment.models.Customer;
import com.demo.fulfillment.models.Order;
import org.springframework.data.util.Pair;

import java.util.List;

public interface OrderCustom {
    public List<Pair<Customer, Order>> getFirstOrderPerCustomer();
}

# OrderCustomDsl Implemented strong-typed querying
package com.demo.fulfillment.repositories;

import com.demo.fulfillment.models.Customer;
import com.demo.fulfillment.models.Order;
import com.demo.fulfillment.models.QCustomer;
import com.demo.fulfillment.models.QOrder;
import com.querydsl.core.Tuple;
import com.querydsl.jpa.impl.JPAQuery;
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
import org.springframework.data.util.Pair;

import java.util.ArrayList;
import java.util.List;

public class OrderCustomDsl extends QuerydslRepositorySupport
        implements OrderCustom {

    public OrderCustomDsl() {
        super(Order.class);
    }

    public List<Pair<Customer, Order>> getFirstOrderPerCustomer() {
        JPAQuery<Tuple> query = new JPAQuery<>(getEntityManager());

        List<Tuple> result = query.join(QCustomer.customer)
        .on(QCustomer.customer.id.eq(QOrder.order.customerId)).fetch();

        List<Pair<Customer, Order>> resultList = new ArrayList<>();
        for (Tuple t : result) {
            resultList.add(Pair.of(t.get(QCustomer.customer), t.get(QOrder.order)));
        }

        return resultList;
    }
}

The only missing piece here is added to the OrderRepository class definition:

        extends JpaRepository<Order, UUID>,
        QuerydslBinderCustomizer<QOrder>,
        QuerydslPredicateExecutor<Order>,
        OrderCustom {

That’s it. Now we can make the Orders controller Autowire the Service instead and voila!

package com.demo.fulfillment.controllers;

import com.demo.fulfillment.models.Customer;
import com.demo.fulfillment.models.Order;
import com.demo.fulfillment.services.OrderService;
import com.querydsl.core.types.Predicate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.querydsl.binding.QuerydslPredicate;
import org.springframework.data.util.Pair;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping(name = "/orders", produces = MediaType.APPLICATION_JSON_VALUE)
public class Orders {

    @Autowired
    OrderService orderService;

    @GetMapping
    @ResponseBody
    List<Pair<Customer, Order>> getCustomerOrders() {
        return orderService.getEachCustomerFirstOrder();
    }

    @GetMapping
    @ResponseBody List<Order> searchOrders(@QuerydslPredicate Predicate predicate) {
        return orderService.searchOrders(predicate);
    }
}

What’s next

Now that we’ve managed to get the core built out in our application, documentation and testing will be next. But with a Ruby-like twist. You can check out the code on my GitHub here.

Thanks for reading this far, if you’ve made it here, give my post a like! Happy to answer any questions I can in my social links as well. Until next time!