Entries in java (11)

Tuesday
Sep032013

Don't Rely on EntityManager.persist() for Immediate Insert

I had always counted on EntityManager's persist() method to immediately insert entities. I would rely on this when writing database integration tests - I'd persist some records, then test my DAO methods to find them.

On my current project, I decided to add a configuration option to allow me to run my datbase integration tests on my development Oracle database rather than my embedded HSQLDB test database - just for an extra sanity check. The tests that tried to persist() and then retrieve those new entities failed. Adding an entityManager.flush() method after the persist() invocations solved the issue.

...But why?

From en.wikibooks.org:

The EntityManager.persist() operation is used to insert a new object into the database. persist does not directly insert the object into the database, it just registers it as new in the persistence context (transaction). When the transaction is committed, or if the persistence context is flushed, then the object will be inserted into the database. If the object uses a generated Id, the Id will normally be assigned to the object when persist is called, so persist can also be used to have an object's Id assigned. The one exception is if IDENTITY sequencing is used, in this case the Id is only assigned on commit or flush because the database will only assign the Id on INSERT. If the object does not use a generated Id, you should normally assign its Id before calling persist.

Here's how I wire up my entities' primary key:

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

For my embedded HSQLDB database, the generation strategy is GenerationType.IDENTIY, which relies on the database to generate an autoincrementing primary key for that row. This requires an insert, so the persist() immediately inserts in HSQLDB.

Oracle, on the other hand, uses a cross-table GenerationType.SEQUENCE @Id generator, which doesn't require an insert, but the following SELECT:

select
    hibernate_sequence.nextval
from
    dual

This select is called immediately on persist() so that the EntityManager has an ID to assign the entity. That entity will only be inserted after a flush(), which is called automatically on transaction commit.

Long story short: If you're relying on your entity existing in the database after your call to persist(), but before the transaction commits, then call flush() first. Leave a comment justifying it, as manually calling flush is largely considered an anti-pattern akin to invoking the garbage collector. Delayed flush() calls give Hibernate the chance to perform more performant bulk updates.

Tuesday
Sep032013

Object/Relational Mapping: Know Your Frameworks

I've been working with Hibernate for several years now, yet I learn something new about it all the time. The more time I spend with the framework, the more concerned I am about how it will be used by developers new to it.

Mirko Novakovic Alois Reitbauer nails it in a post about O/R Mapping Anti-Patterns:

The simplicity of the entrance into the world of O/R mapping however gives a wrong impression of the complexity of these frameworks. Working with more complex applications you soon realize that you should know the details of framework implementation to be able to use them in the best possible way. In this article, we describe some common anti-patterns which may easily lead to performance problems.

This is an echo of Joel Spolsky's warnings of the Law of Leaky Abstraction:

The law of leaky abstractions means that whenever somebody comes up with a wizzy new code-generation tool that is supposed to make us all ever-so-efficient, you hear a lot of people saying "learn how to do it manually first, then use the wizzy tool to save time." Code generation tools which pretend to abstract out something, like all abstractions, leak, and the only way to deal with the leaks competently is to learn about how the abstractions work and what they are abstracting. So the abstractions save us time working, but they don't save us time learning.

Don't stop learning about a framework once you figure out how to use it - that's only the beginning.

Sunday
Jun232013

Apache Camel: Powerful EIP Framework

Pradeep Elankumaran gives a high-level overview of Apache Camel

From Apache Camel's project page:

Apache Camel ™ is a versatile open-source integration framework based on known Enterprise Integration Patterns.

Camel empowers you to define routing and mediation rules in a variety of domain-specific languages, including a Java-based Fluent API, Spring or Blueprint XML Configuration files, and a Scala DSL. This means you get smart completion of routing rules in your IDE, whether in a Java, Scala or XML editor.

Apache Camel uses URIs to work directly with any kind of Transport or messaging model such as HTTP, ActiveMQ, JMS, JBI, SCA, MINA or CXF, as well as pluggable Components and Data Format options. Apache Camel is a small library with minimal dependencies for easy embedding in any Java application. Apache Camel lets you work with the same API regardless which kind of Transport is used - so learn the API once and you can interact with all the Components provided out-of-box.

Apache Camel provides support for Bean Binding and seamless integration with popular frameworks such as Spring, Blueprint and Guice. Camel also has extensive support for unit testing your routes.

Check out "What exactly is Apache Camel?" on StackOverflow for other devs' descriptions.

Thursday
Mar072013

Java: Copy/Paste-Safe Logger Creation

We've all copied and pasted a Logger from one class to the other, forgetting to change the class name.

// WRONG
public class SomeNewClass
{
    // OOPS! I forgot to change the class name!
    private final Logger _logger = LoggerFactory.getLogger(SomeOtherClass.class);

    // …  
}

Here, use this smarter LoggerFactory which uses the stack trace to figure out what class you really mean. This is safe from copypasta laziness.

package org.blakecaldwell.logging;

import org.slf4j.Logger;

/**
 * Smart logger that uses reflection to figure out the logged class
 */
public final class ClassLoggerFactory
{
    /**
     * Disallow factory instances.
     */
    private ClassLoggerFactory()
    {
    }

    /**
     * Use the stack trace to determine the appropriate logger.
     * 
     * @return a logger for the direct caller's class.
     */
    public static Logger make()
    {
        Throwable t = new Throwable();
        StackTraceElement directCaller = t.getStackTrace()[1];
        return org.slf4j.LoggerFactory.getLogger(directCaller.getClassName());
    }
}

Then, to use this logger:

// CORRECT
public class SomeNewClass
{
    // No chance for screwing up anymore!
    private final Logger _logger = ClassLoggerFactory.make();

    // …  
}
Wednesday
Mar062013

Java: Embedding Spring Batch Admin Into An Existing Application

For my current project, I need to share Spring beans between my front-end web servlets and the Spring Batch jobs that I'll launch from Spring Batch Admin (SBA). This sounds straightforward - configure the SBA servlet to listen on /batch/*, and you should be good to go. However, of course it's not that easy.

URL Path Mismatch

The first problem you run into is bad links in the menus, form posts that go nowhere, and missing CSS files. This is because SBA expects to be deployed on /, not /batch, or /myapp/batch which is where it will go if you deploy it inside another app. You can survive in this mode for the most part by correcting menu URLs after you click them, or use FireBug to change form actions before you submit them - for example, from /batch/files to /myapp/batch/files, but who wants to live like an animal?

Spring Batch Admin 1.2.1 to the Rescue

Spring Batch Admin version 1.2.1 adds the ability to set the base servlet path for all links and forms by overriding the "resourceService" bean. I'm sure there are several ways to successfully accomplish this, but here's what worked for me.

pom.xml

Add the following repository:

<repository>
    <id>repository.springframework.maven.release</id>
    <name>Spring Framework Maven Release Repository</name>
    <url>http://maven.springframework.org/release</url>
</repository>

and the dependencies:

<dependency>
    <groupId>org.springframework.batch</groupId>
    <artifactId>spring-batch-admin-resources</artifactId>
    <version>1.2.1.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.batch</groupId>
    <artifactId>spring-batch-admin-manager</artifactId>
    <version>1.2.1.RELEASE</version>
</dependency>

web.xml

Configure the Batch Admin Servlet - notice contextConfigLocation:

<servlet>
    <servlet-name>Batch Admin Servlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/batch-admin/batch-admin-context.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>Batch Admin Servlet</servlet-name>
    <url-pattern>/batch/*</url-pattern>
</servlet-mapping>

/WEB-INF/spring/batch-admin/batch-admin-context.xml

This is the context file that's only used by SBA. Keep in mind that any beans defined in your root Spring context will also be available to the jobs that are launched by SBA.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- Spring Batch Admin Context: Additional context for Spring Batch Admin -->
    <import resource="classpath*:/META-INF/spring/batch/servlet/resources/*.xml" />
    <import resource="classpath*:/META-INF/spring/batch/servlet/manager/*.xml" />
    <import resource="classpath*:/META-INF/spring/batch/servlet/override/*.xml" />
    <import resource="classpath*:/META-INF/spring/batch/bootstrap/**/*.xml" />
    <import resource="classpath*:/META-INF/spring/batch/override/**/*.xml" />

    <!-- For Spring Batch -->
    <bean id="resourceService"
        class="org.springframework.batch.admin.web.resources.DefaultResourceService">
        <property name="servletPath" value="/batch" />
    </bean>
</beans>

One Last Gotcha - /myapp/batch

The main "Home" link in the SBA action bar navigates to /myapp/batch. This doesn't work the way I've wired it up, because the SBA servlet is configured to listen to /myapp/batch/, but not /myapp/batch. I tried adding another servlet-mapping to /batch, but the servlet won't answer requests to it.

I'm sure there's a better way to do this via config only - please tell me if you wouldn't mind, but I opted for the more manual way, just so I could move on.

I wired up a servlet in my main web app to listen to /batch, and then have it redirect to /batch/.

web.xml - configuration for a servlet in my main app

Pay attention to the last servlet-mapping. This gives /myapp/batch over to Spring MVC for URL mapping.

<servlet>
    <servlet-name>appServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
    </init-param>
    <async-supported>true</async-supported>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>appServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

<!-- LOOK HERE -->
<servlet-mapping>
    <servlet-name>appServlet</servlet-name>
    <url-pattern>/batch</url-pattern>
</servlet-mapping>

HomeController.java

Here's where I redirect /batch to /batch/. Again, I'm sure there's a better way to do this (let me know!). Of course, for this specific code to work, you'll need some dependencies such as spring-webmvc. The bottom line is that I'm just manually listening on /batch and redirecting to /batch/.

@Controller
public class HomeController
{
    /**
     * Redirect the url /batch to /batch/ for the Spring Batch Admin to pick it up.
     * 
     * @return redirect to /batch/
     */
    @RequestMapping(value = "/batch", method = RequestMethod.GET)
    public String redirectBatchToBatchSlash()
    {
        return "redirect:/batch/";
    }
}

Know a Better Way?

Ideally, I'd like to either get Spring Batch Admin to listen to /batch or /myapp/batch, but I'd settle on having a redirect configured in web.xml. Please tell me if you know of a good approach. I moved on with this good-enough solution.