Category Archives for JPA

Thanks to Mayra Carreno @ https://unsplash.com/@mayracarreno?utm_campaign=photographer-credit

Data Hiding using JsonIgnore and Spring Data JPA

Data Hiding using JsonIgnore and Spring Data JPA

Data Hiding using JsonIgnore and Spring Data JPA is achieved using two approaches –

  • @JsonIgnore and @JsonIgnoreProperties
  • Repository Detection Strategies

This post considers @JsonIgnore and @JsonIgnoreProperties

Code

The code is available at –

Code Changes

I’ve added an extra table to for this example –

@Entity
public class Secrets {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
private String mySecrets;
public String getMySecrets() {
return mySecrets;
}
public void setMySecrets(String mySecrets) {
this.mySecrets = mySecrets;
}
}

With its associated repository –

@PreAuthorize("hasRole('ROLE_USER')")
public interface SecretsRepository extends CrudRepository<Secrets, Long> {
}

Running The Code

I have left the security from the last tutorial, Securing Spring Data REST with PreAuthorize, in place – but we can run this code using –

mvnw spring-boot:run

We can then call rest/profile to see the two exposed repositories –

curl -u user:user -X GET http://localhost:8080/rest/profile
{
"_links" : {
"self" : {
"href" : "http://localhost:8080/rest/profile"
},
"secrets" : {
"href" : "http://localhost:8080/rest/profile/secrets"
},
"parkrunCourses" : {
"href" : "http://localhost:8080/rest/profile/parkrunCourses"
}
}
}

And calling the secrets REST end point-

curl -u user:user -X GET http://localhost:8080/rest/secrets/1
{
"mySecret" : "I want to hide this",
"_links" : {
"self" : {
"href" : "http://localhost:8080/rest/secrets/1"
},
"secret" : {
"href" : "http://localhost:8080/rest/secrets/1"
}
}
}

This posts looks at techniques I can use to not expose the SecretRepository

@JsonIgnore and @JsonIgnoreProperties

The purpose of @JsonIgnore, and @JsonIgnoreProperties is to hide attributes from the Jackson parser by instructing it to Ignore these fields

Usage is simply a matter of tagging the attribute with the @JsonIgnore

@Entity
public class Secret {
//
@JsonIgnore
private String mySecret;
//
}

Or we can achieve the same using @JsonIgnoreProperties annotation –

@JsonIgnoreProperties({"mySecret"})
@Entity
public class Secret {
//
private String mySecret;
//
}

With either of these changes we can then call our secrets REST end point, and the mySecret field is no longer exposed –

curl -u user:user -X GET http://localhost:8080/rest/secrets/1
{
"_links" : {
"self" : {
"href" : "http://localhost:8080/rest/secrets/1"
},
"secret" : {
"href" : "http://localhost:8080/rest/secrets/1"
}
}
}

Conclusion

@JsonIgnore or @JsonIgnoreProperties simply hides the field from the Jackson parser. This is good for hiding small pieces of information. The downside is we still have an exposed end point due to the default Repository Detection Strategies.

 

 

Introduction to Spring Data REST

This tutorial on Spring Data REST shows how Spring Data repositories can be exposed as a REST API. Its a really interesting idea, and can save you a lot of boilerplate code building microservices.

Introduction to Spring Data REST

Spring Boot makes it easy to create a Spring Data REST starter project using Spring Initializr

spring initializr

Spring Initializr – Spring Boot JPA Microservice

Im using Spring Boot 2.0.0, and the HAL Browser to quickly demonstrate REST API.

Download, expand and import into your IDE –

Expanded Spring Boot Project

Expanded Spring Boot Project

Code

For a quickstart the code is available on github at – https://github.com/farrelmr/introtospringdatarest/tree/1.0.0

Let’s keep this simple and use the classes from my parkrunpb project.

The starting point is the pom.xml –

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.javabullets.springdata</groupId>
    <artifactId>jparest</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>jparest</name>
    <description>Spring Data Rest Example</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.BUILD-SNAPSHOT</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-rest</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-rest-hal-browser</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
        <pluginRepository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>
</project>

So we have our JPA object –

package com.javabullets.springdata.jparest;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class ParkrunCourse {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;

private String courseName;
private String url;
private Long averageTime;

public String getCourseName() {
return courseName;
}
public void setCourseName(String courseName) {
this.courseName = courseName;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Long getAverageTime() {
return averageTime;
}
public void setAverageTime(Long averageTime) {
this.averageTime = averageTime;
}
}

Im using JPARepository –

package com.javabullets.springdata.jparest;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;

public interface ParkrunCourseRepository extends JpaRepository<ParkrunCourse, Long> {
}

And a script to ensure the data is preloaded – stored in (src\main\resources\import.sql) –

INSERT INTO PARKRUN_COURSE(ID, COURSE_NAME, URL, AVERAGE_TIME) VALUES (1, 'Inverness', 'http://www.parkrun.org.uk/inverness/', 1582);
INSERT INTO PARKRUN_COURSE(ID, COURSE_NAME, URL, AVERAGE_TIME) VALUES (2, 'Aberdeen',    'http://www.parkrun.org.uk/aberdeen/', 1586);
INSERT INTO PARKRUN_COURSE(ID, COURSE_NAME, URL, AVERAGE_TIME) VALUES (3, 'Dundee(Camperdown)', 'http://www.parkrun.org.uk/camperdown/', 1752);
INSERT INTO PARKRUN_COURSE(ID, COURSE_NAME, URL, AVERAGE_TIME) VALUES (4, 'St Andrews', 'http://www.parkrun.org.uk/standrews/', 1669);
INSERT INTO PARKRUN_COURSE(ID, COURSE_NAME, URL, AVERAGE_TIME) VALUES (5, 'Perth', 'http://www.parkrun.org.uk/perth/', 1620);
INSERT INTO PARKRUN_COURSE(ID, COURSE_NAME, URL, AVERAGE_TIME) VALUES (6, 'Edinburgh', 'http://www.parkrun.org.uk/edinburgh/', 1523);

Running the Code

Run the code using the maven wrapper – mvnw –

mvnw spring-boot:run

We can then access the HALBrowser on –

http://localhost:8080/browser/index.html#http://localhost:8080/parkrunCourses

Spring Data REST - HALBrowser

Spring Data REST – HALBrowser

The response body returns all the parkrunCourse. You can also do GET, POST, PUT, PATCH and DELETE.

A good trick is to use the browser integrated SQL editor for the H2 database. I covered this in my spring boot security tutorial.

hal+json media type

Spring Data JPA allows you to make calls as json or hal+json. The purpose of hal+json makes it easier to navigate API’s following links. The HALBrowser is great for quickly checking your API.

Conclusions

This example shows how a spring data repository can easily be exposed as a REST API. My next post will look at the practicality of this approach, and what restrictions you might want to apply to your API.

JPQL vs Criteria API

Ok this is ongoing and I’ll fill in the blanks as I get time

Simple Queries

SELECT ag FROM Agreement ag

[sourcecode lang=”java”] CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Agreement> cq = cb.createQuery(Agreement.class);
Root<Agreement> agreement = cq.from(Agreement.class);
cq.select(agreement);
TypedQuery<Agreement> q = em.createQuery(cq);
List<Agreement> allAgreements = q.getResultList();
[/sourcecode]

SELECT DISTINCT ag.agreementYear FROM Agreement ag

[sourcecode lang=”java”] CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Agreement> cq = cb.createQuery(Agreement.class);
Root<Agreement> agreement = cq.from(Agreement.class);
cq.select(agreement).distinct(true); // distinct == true
TypedQuery<Agreement> q = em.createQuery(cq);
List<Agreement> allAgreements = q.getResultList();
[/sourcecode]

SELECT ag FROM Agreement ag WHERE ag.agreementYear = ‘2014’

[sourcecode lang=”java”] // Without MetaModel
cq = cb.createQuery(Agreement.class);
Root<Agreement> agreement = cq.from(Agreement.class);
cq.select(agreement).where(
cb.equal(agreement.get("agreementYear"), "2014")
);

// Without MetaModel – typesafety thru metamodel
cq = cb.createQuery(Agreement.class);
Root<Agreement> agreement = cq.from(Agreement.class);
cq.select(agreement).where(cb.equal(
agreement.get(Agreement_.agreementYear), 2014)
);
[/sourcecode]

The rest of the examples will use the metamodel.

SELECT ag FROM Agreement ag WHERE ag.agreementName LIKE ‘%test%’

[sourcecode lang=”java”] cq = cb.createQuery(Agreement.class);
Root<Agreement> agreement = cq.from(Agreement.class);
cq.select(agreement).where( cb.like(
agreement.get(Agreement_.agreementName), "%test%" )
)
);
[/sourcecode]

SELECT ag FROM Agreement ag WHERE ag.agreementName IS NULL

[sourcecode lang=”java”] cq = cb.createQuery(Agreement.class);
Root<Agreement> agreement = cq.from(Agreement.class);
cq.select(agreement).where(
agreement.get(Agreement_.agreementName).isNull()
);
[/sourcecode]

SELECT ag FROM Agreement ag WHERE ag.claims IS EMPTY

[sourcecode lang=”java”] cq = cb.createQuery(Agreement.class);
Root<Agreement> agreement = cq.from(Agreement.class);
cq.select(agreement).where(
cb.isEmpty(agreement.<Collection>get("claims"))
);
[/sourcecode]

Joins

The above queries are nice – but the real power of a query language is JOIN’s

JOIN == INNER JOIN

SELECT ag FROM Agreement ag JOIN ag.claims cl

[sourcecode lang=”java”] CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Agreement> query = cb.createQuery(Agreement.class);
Root<Agreement> agreement = query.from(Agreement.class);
Join<Claim, Agreement> claims = agreement.join("claims");
query.select(claims);
List<Claim> claims = em.createQuery(query).getResultList();
[/sourcecode]

SELECT ag FROM Agreement ag JOIN ag.claims cl WHERE cl.amount = 1000

[sourcecode lang=”java”] CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Agreement> query = cb.createQuery(Agreement.class);
Root<Agreement> agreement = query.from(Agreement.class);
Join<Claim, Agreement> claims = agreement.join("claims");
query.select(claims).where(cb.equal(agreement .get("amount"), 1000));
List<Claim> claims = em.createQuery(query).getResultList();
[/sourcecode]

LEFT JOIN == LEFT OUTER JOIN

SELECT ag FROM Agreement ag LEFT JOIN ag.claims cl WHERE cl.amount > 1000

[sourcecode lang=”java”] CriteriaQuery cq = cb.createQuery(Agreement.class);
Root<Agreement> = cq.from(Agreement.class);
SetJoin claims = agreement.join(Agreement_.claims, JoinType.LEFT);
cq.select(agreement).where( cb.equal(claims.get(Claim_.amount), 1000) );
List result = em.createQuery(cq).getResultList();
[/sourcecode]

FETCH JOIN

A FETCH JOIN enables the fetching of an association as a side effect of the execution of a query

The effect of the FETCH is to not return the associated claims

SELECT ag FROM agreement ag LEFT JOIN FETCH ag.claims cl WHERE cl.amount = 1000

[sourcecode lang=”java”] CriteriaQuery cq = cb.createQuery(Agreement.class);
Root<Agreement> agreement = cq.from(Agreement.class);
SetJoin<Agreement, Claims> claims = agreement.join(Agreement_.claims, JoinType.LEFT);
cq.select(agreement).where( cb.equal(claims.get(Claim_.amount), 1000) );
agreement.fetch("claims");
List result = em.createQuery(cq).getResultList();
[/sourcecode]

References

http://en.wikibooks.org/wiki/Java_Persistence/Criteria
http://docs.oracle.com/javaee/6/tutorial/doc/gjrij.html