© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
J. B. Ottinger et al.Beginning Hibernate 6https://doi.org/10.1007/978-1-4842-7337-1_1

1. An Introduction to Hibernate 6

Joseph B. Ottinger1  , Jeff Linwood2 and Dave Minter3
(1)
Youngsville, NC, USA
(2)
Austin, TX, USA
(3)
London, UK
 

Most significant development projects involve a relational database.1 The mainstay of most commercial applications is the large-scale storage of ordered information, such as catalogs, customer lists, contract details, published text, and architectural designs.

With the advent of the World Wide Web, the demand for databases has increased. Though they may not know it, the customers of online bookshops and newspapers are using databases. Somewhere in the guts of the application, a database is being queried and a response is offered.

Hibernate is a library (actually, a set of libraries) that simplifies the use of relational databases in Java applications by presenting relational data as simple Java objects, accessed through a session manager, therefore earning the description of being an “Object/Relational Mapper,” or ORM. It provides two kinds of programmatic interfaces: a “native Hibernate” interface and the Jakarta EE2 standard Java Persistence API.

This edition focuses on Hibernate 6. As this sentence is being written, it’s still at Alpha8, so it’s not formally released, but chances are that the actual release is going to be very similar to the code used here.

There are solutions for which an ORM – like Hibernate – is appropriate and some for which the traditional approach of direct access via the Java Database Connectivity (JDBC) API is appropriate. We think that Hibernate represents a good first choice, as it does not preclude the simultaneous use of alternative approaches, even though some care must be taken if data is modified from two different APIs.

To illustrate some of Hibernate’s strengths, in this chapter we take a look at a brief example using Hibernate and contrast this with the traditional JDBC approach.

Plain Old Java Objects (POJOs)

Java, being an object-oriented language, deals with objects. Usually, objects that represent program states are fairly simple, containing properties (or attributes) and methods that alter or retrieve those attributes (mutators and accessors, known as “setters” and “getters,” colloquially). In general, these objects might encapsulate some behavior regarding the attributes, but usually their sole purpose is containing a program state. These are known typically as “plain old Java objects,” or POJOs.

In an ideal world, it would be trivial to take any Java object – plain or not – and persist it to a database. No special coding would be required to achieve this, no performance penalty would ensue, and the result would be totally portable. In this ideal world, we would perhaps perform such an operation in a manner like this.
POJO pojo=new POJO();
ORMSolution magic=ORMSolution.getInstance();
magic.save(pojo);
Listing 1-1.

A Rose-Tinted View of Object Persistence

There would be no nasty surprises, no additional work to correlate the class with what the table schema might be, and no performance problems.

Hibernate actually comes remarkably close to this idea, at least when compared with many of its alternatives,3 but there are configuration files to create and subtle performance and synchronization issues to consider. Hibernate does, however, achieve its fundamental aim: it allows you to trivially store POJOs in the database. Figure 1-1 shows how Hibernate fits into your application between the client code and the database.
../images/321250_5_En_1_Chapter/321250_5_En_1_Fig1_HTML.png
Figure 1-1

The role of Hibernate in a Java application

Building a Project

We’re going to use Maven (https://maven.apache.org) to build a project for this book. It’ll be organized as a top-level project with a subproject (or “module”) for every chapter, and we’ll also have a few extra modules to provide common functionality. You can do the same thing with Gradle (https://gradle.org), and there’s no real reason for this book to prefer one over the other, but Maven won the coin toss,4 so Maven it is.

Create a directory on your filesystem; it can be anything you like. (On my system, it’s /Users/joeo/work/publishing/bh6/src, but you can name it anything appropriate for your tastes and preference and, obviously, filesystem.) This will be our top-level directory; we’re going to put chapters in it by name, like chapter01 and the like. We’re using two digits because it looks nicer and also collates properly.5
<project 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"
         xmlns:="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.autumncode.books.hibernate</groupId>
    <artifactId>hibernate-6-parent</artifactId>
    <packaging>pom</packaging>
    <version>5.0</version>
    <modules>
        <module>chapter01</module>
    </modules>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <testng.version>7.4.0</testng.version>
        <hibernate.core.version>6.0.0.Alpha8</hibernate.core.version>
        <h2.version>1.4.200</h2.version>
        <logback.version>1.2.3</logback.version>
        <lombok.version>1.18.18</lombok.version>
        <hibernate.validator.version>
            6.2.0.Final
        </hibernate.validator.version>
        <javax.el-api.version>3.0.0</javax.el-api.version>
        <ignite.version>2.10.0</ignite.version>
        <jackson.version>2.12.3</jackson.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.hibernate.orm</groupId>
                <artifactId>hibernate-core</artifactId>
                <version>${hibernate.core.version}</version>
            </dependency>
            <dependency>
                <groupId>org.testng</groupId>
                <artifactId>testng</artifactId>
                <version>${testng.version}</version>
            </dependency>
            <dependency>
                <groupId>com.h2database</groupId>
                <artifactId>h2</artifactId>
                <version>${h2.version}</version>
            </dependency>
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>${logback.version}</version>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
            </dependency>
            <dependency>
                <groupId>org.apache.ignite</groupId>
                <artifactId>ignite-core</artifactId>
                <version>${ignite.version}</version>
            </dependency>
            <dependency>
                <groupId>org.hibernate.orm</groupId>
                <artifactId>hibernate-jcache</artifactId>
                <version>${hibernate.core.version}</version>
            </dependency>
            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-envers</artifactId>
                <version>${hibernate.core.version}</version>
            </dependency>
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
                <version>${jackson.version}</version>
            </dependency>
            <dependency>
                <groupId>com.fasterxml.jackson.datatype</groupId>
                <artifactId>jackson-datatype-jsr310</artifactId>
                <version>${jackson.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.hibernate.orm</groupId>
            <artifactId>hibernate-core</artifactId>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <release>11</release>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
Listing 1-2

The Top-Level pom.xml

So what is this pom.xml actually doing? It turns out, quite a bit – although almost all of it’s related to common configuration for the rest of the book, so the module pom.xml files are much simpler than they otherwise would be.

The first few lines describe the “parent project,” described as having a groupId of com.autumncode.books.hibernate and an artifact id of hibernate-6-parent. It’s also set to a version of 1.0-SNAPSHOT – none of this is particularly relevant.

We then have a <modules> block, with a single module in it. As we progress through the book, we’ll add modules here for each chapter, and if you look at the source code that accompanies this book, you’ll see a full complement of modules in this section.

Up next is the <properties> block, which we use to set the default compiler version and target (Java 11, which is the current “production” version of Java6), followed by a lot of specific dependency versions, like <h2.version>1.4.200</h2.version>.7

Next, we have a <dependencyManagement> block. This doesn’t actually set up any dependencies, as such: it simply allows us to centralize references for dependencies. Note that modules will inherit the dependencies of the parent project, so we can declare all of our specific dependencies here and the modules can simply use the names, instead of having to include the versions as well. If a new version of Hibernate were to come out, for example, we’d only have to change the version used in <dependencyManagement>, and the change would propagate throughout the whole project.

After <dependencyManagement>, we have dependencies we expect to be common for the entire project. This is a Hibernate book, so it makes sense to have Hibernate itself here, as well as a relatively standard logging framework (Logback (https://logback.qos.ch/), which itself includes Slf4j (www.slf4j.org/) as a transitive dependency), and we also import TestNG (https://testng.org) and H2 (www.h2database.com/), a popular embedded database written in pure Java, as test dependencies.

Lastly, we have a <build> section, which forces Maven to use a recent version of the maven-compiler-plugin, required to set the language version correctly, because while Maven is horribly useful, it’s also designed to support legacy JVMs very well, to the point where you have to tell it specifically to use more recent JVMs.

We haven’t even gotten to this chapter’s build yet! We declared the module, but we haven’t described it yet. Thankfully, with so much work being done in the parent pom.xml, the chapter’s project model is really rather simple.
<?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">
    <parent>
        <groupId>com.autumncode.books.hibernate</groupId>
        <artifactId>hibernate-6-parent</artifactId>
        <version>5.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>chapter01</artifactId>
</project>
Listing 1-3

chapter01/pom.xml

Here, all we do is declare what this module is (chapter01) and include a reference to the parent module. Everything is inherited.

Now that we have all the icky project stuff out of the way, let’s circle back around to what Hibernate is designed to do for us.

Hibernate is, as already pointed out, an “Object/Relational Mapper,” meaning that it maps Java objects to a relational schema, and vice versa. Programmers actually have a lot of control over what the actual mapping looks like, but in general it’s easiest to follow a few simple idioms to create easily mapped objects. Let’s start with a simple object, representing a message that we’ll store in a database for absolutely no good reason at all, other than to serve as the basis for a simple example.
package chapter01.pojo;
import java.util.Objects;
public class Message {
  String text;
  public Message(String text) {
    setText(text);
  }
  public String getText() {
    return text;
  }
  public void setText(String text) {
    this.text = text;
  }
  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Message)) return false;
    Message message = (Message) o;
    return Objects.equals(getText(), message.getText());
  }
  @Override
  public int hashCode() {
    return Objects.hash(getText());
  }
  @Override
  public String toString() {
    return String.format("Message{text='%s'}", getText());
  }
}
Listing 1-4

chapter01/src/main/java/chapter01/pojo/Message.java

You can’t get much simpler than that object; it’s doable, of course, because you could create an object that had no state (therefore, no text field and no accessor or mutator to reference to it), and we could ignore the equals() and hashCode() and toString() as well. Such objects would be useful as actors, objects that act on other objects. But Listing 1-4 is a good example of what most POJOs will look like, in that most Java classes that represent program state will have attributes, accessors, mutators, equals, hashCode, and toString as well.

Hibernate could map Listing 1-4 fairly easily, but it does not follow the idioms you’ll find in most Hibernate entities. Getting there is really simple, though.

Let’s create a MessageEntity class , which still doesn’t fit the Hibernate idiom perfectly, but is ready for persistence – and along the way, it’ll serve as the basis for an actual Hibernate entity, which we’ll see right after we see what Hibernate actually does for us behind the scenes.
package chapter01.pojo;
import java.util.Objects;
public class MessageEntity {
  Long id;
  String text;
  public MessageEntity() {
  }
  public MessageEntity(Long id, String text) {
    this();
    setId(id);
    setText(text);
  }
  public Long getId() {
    return id;
  }
  public void setId(Long id) {
    this.id = id;
  }
  public String getText() {
    return text;
  }
  public void setText(String text) {
    this.text = text;
  }
  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof MessageEntity)) return false;
    MessageEntity message = (MessageEntity) o;
    return Objects.equals(getId(), message.getId())
        && Objects.equals(getText(), message.getText());
  }
  @Override
  public int hashCode() {
    return Objects.hash(getId(), getText());
  }
  @Override
  public String toString() {
    return String.format("MessageEntity{id=%d,text='%s'}",
        getId(),
        getText());
  }
}
Listing 1-5

chapter01/src/main/java/chapter01/pojo/MessageEntity.java

There are a few changes here. We’ve added an id field (a Long), along with an accessor and a mutator for the id, and we’ve added the id to the standard utility methods (equals(), hashCode(), and toString())…​ and we’ve added a no-argument constructor. The id field is pretty common for relational mapping, because such fields are much easier to search and refer to when working with the database, but the no-argument constructor is primarily a concession to convenience, because it allows us to create a sort of “blank canvas” object that we can fill in later, through the mutators or by direct field access, if we allow that.

In strict OOP terms, this can be a bad thing, because it means we can legitimately construct an object that lacks legitimate state; consider our poor old MessageEntity. If we define a “valid MessageEntity” as having a valid id field (any number will do, as long as it’s not null) and a populated text field (anything but null), then calling our no-argument constructor creates a MessageEntity that is not valid. In fact, if we call the other constructor, we have similar problems, because we aren’t checking the values of the attributes as we set them.

This is actually a characteristic of the Java Persistence API, or JPA specification, which says that the class must have a public or protected no-argument constructor with no arguments. Hibernate extends the JPA specification, and while it is looser in some requirements than the JPA specification is, it generally follows the constructor requirement (although the constructor can also have package visibility).

We also should not have the class be marked final. There are actually ways around this, but Hibernate by default creates an extension of the class to enable some potentially very useful features (like lazy-loading data in attributes).

You should also provide standard accessors and mutators (like getId() and setId()).

So how would we use this class in an actual persistence story? Here’s a test class that actually initializes a database, saves a MessageEntity into it, and then tests that the message can properly be retrieved.
package chapter01.jdbc;
import chapter01.pojo.MessageEntity;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import static org.testng.Assert.assertEquals;
public class PersistenceTest {
  Connection getConnection() throws SQLException {
    return DriverManager.getConnection("jdbc:h2:./db1", "sa", "");
  }
  @BeforeClass
  public void setup() {
    final String DROP = "DROP TABLE messages IF EXISTS";
    final String CREATE = "CREATE TABLE messages ("
        + "id BIGINT GENERATED BY DEFAULT AS IDENTITY "
        + "PRIMARY KEY, "
        + "text VARCHAR(256) NOT NULL)";
    try (Connection connection = getConnection()) {
      // clear out the old data, if any, so we know the state of the DB
      try (PreparedStatement ps =
               connection.prepareStatement(DROP)) {
        ps.execute();
      }
      // create the table...
      try (PreparedStatement ps =
               connection.prepareStatement(CREATE)) {
        ps.execute();
      }
    } catch (SQLException e) {
      e.printStackTrace();
      throw new RuntimeException(e);
    }
  }
  public MessageEntity saveMessage(String text) {
    final String INSERT = "INSERT INTO messages(text) VALUES (?)";
    MessageEntity message = null;
    try (Connection connection = getConnection()) {
      try (PreparedStatement ps =
               connection.prepareStatement(INSERT,
                   Statement.RETURN_GENERATED_KEYS)) {
        ps.setString(1, text);
        ps.execute();
        try (ResultSet keys = ps.getGeneratedKeys()) {
          if (!keys.next()) {
            throw new SQLException("No generated keys");
          }
          message = new MessageEntity(keys.getLong(1), text);
        }
      }
    } catch (SQLException e) {
      e.printStackTrace();
      throw new RuntimeException(e);
    }
    return message;
  }
  @Test
  public void readMessage() {
    final String text = "Hello, World!";
    MessageEntity message = saveMessage(text);
    final String SELECT = "SELECT id, text FROM messages";
    List<MessageEntity> list = new ArrayList<>();
    try (Connection connection = getConnection()) {
      try (PreparedStatement ps =
               connection.prepareStatement(SELECT)) {
        try (ResultSet rs = ps.executeQuery()) {
          while (rs.next()) {
            MessageEntity newMessage = new MessageEntity();
            newMessage.setId(rs.getLong(1));
            newMessage.setText(rs.getString(2));
            list.add(message);
          }
        }
      }
    } catch (SQLException e) {
      e.printStackTrace();
      throw new RuntimeException(e);
    }
    assertEquals(list.size(), 1);
    for (MessageEntity m : list) {
      System.out.println(m);
    }
    assertEquals(list.get(0), message);
  }
}
Listing 1-6

chapter01/src/test/java/chapter01/jdbc/PersistenceTest.java

So what’s going on here? First, we have a simple utility method that returns a Connection; this mostly saves the length of the statement and reduces repetition.

We also have a setup() method , marked with @BeforeClass. This annotation means this method will be invoked before any of the tests in the class are executed. (We could also have used @BeforeTest or @BeforeSuite, but in this case, @BeforeClass is probably the right granularity, assuming we have more functionality to test than we actually do.)

The annotations indicate when the annotated method runs, in context of the test class. @BeforeTest runs before every method annotated with @Test runs. @BeforeClass runs before any test method in the class runs; @BeforeSuite runs before any test in any test class runs. There are also @AfterClass, @AfterTest, and @AfterSuite methods that run at the end of their corresponding phases.

Next, we have another utility method, saveMessage(), which takes message text to save. This will insert a new record in the database table. It requests the generated key from the database so that it can populate a MessageEntity and return it, mirroring convenient behavior (we can now query for that message and test for equivalency, as we see done in the readMessage() test ). It’s functional; it’s not very good, honestly, but it’s not worth improving. Hibernate does this sort of thing far better than we are here, and in less code; we could mirror much of what Hibernate does, but it’s just more effort than it’s worth.

Lastly, we have our actual test: readMessage(). This calls saveMessage() and then reads through all of the “saved messages” – which, given that we’ve taken pains to create a deterministic database state, will be a list of one message. As it reads the messages, it creates MessageEntity objects for each one and stores them in a List, and then we validate the List – it should have one element only, and that element should match the MessageEntity we saved at the beginning of the method.

Whew! That’s a lot of work; there’s some boilerplate, in the acquisition of resources (done with automatic resource management, to handle clean deallocation in the case of exceptions), and the JDBC code itself is pretty low level. It’s also rather underpowered and very manual. We’re still managing the specific resources like Connection and PreparedStatement, and the code’s very brittle; if we added a field, we’d have to look for and modify every statement that would be affected by that field, since we’re manually mapping the data from JDBC into our object.8

We also run into the issue of types with this code. This is a very simple object, after all; it stores a simple numeric identifier with a simple string. However, if we wanted to store a geolocation, we’d have to break the geolocation into its component properties (latitude and longitude, for example) and store each separately, which means your object model no longer cleanly matches your database.

All of this makes using the database directly look more and more flawed, and that’s before factoring in other issues around object persistence and retrieval.

Want to run these tests? It’s really simple: run the Maven lifecycle with mvn build, which will download all of the dependencies for our project (if necessary), compile our “production” classes (the ones in src/main/java), then compile our test classes (the ones in src/test/java), and then execute the tests, dumping any console output (to the console, naturally) and halting on failures. It then builds a jar of our production resources. We can also limit the lifecycle to only doing enough to run the tests with mvn test.

Hibernate As a Persistence Solution

Hibernate fixes almost everything we don’t like about the JDBC solution. We don’t use complex types in this example, so we won’t see how that’s done until later in this book, but it’s easier to do in nearly every metric.9

First, we need to fix our MessageEntity to be an actual Hibernate entity. We do this by adding some annotations to the class, making it conform to the JPA requirements. We’re also going to change the constructors slightly to fit the domain of a Message in a better fashion; a Message’s core attribute is its text, and the id is incidental. We can map that better with Hibernate than in our MessageEntity.10 There are four annotations we want to add for JPA, and they actually cover the annotations Hibernate users will use most often:11
  1. 1.

    @javax.persistence.Entity: Which marks the class as an entity class to be managed by Hibernate

     
  2. 2.

    @javax.persistence.Id: Which makes the field to which it applies as a primary key for the database

     
  3. 3.

    @javax.persistence.GeneratedValue: Which provides information to Hibernate about how the value should be populated

     
  4. 4.

    @javax.persistence.Column: Which allows us to control aspects of the field in the database

     
Here’s the Message entity itself.
package chapter01.hibernate;
import javax.persistence.*;
import java.util.Objects;
@Entity
public class Message {
  Long id;
  String text;
  public Message() {
  }
  public Message(String text) {
    this();
    setText(text);
  }
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  public Long getId() {
    return id;
  }
  public void setId(Long id) {
    this.id = id;
  }
  @Column(nullable = false)
  public String getText() {
    return text;
  }
  public void setText(String text) {
    this.text = text;
  }
  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Message)) return false;
    Message message = (Message) o;
    return Objects.equals(getId(), message.getId())
        && Objects.equals(getText(), message.getText());
  }
  @Override
  public int hashCode() {
    return Objects.hash(getId(), getText());
  }
  @Override
  public String toString() {
    return String.format("Message{id=%d,text='%s'}",
        getId(),
        getText());
  }
}
Listing 1-7

chapter01/src/main/java/chapter01/hibernate/Message.java

The @GeneratedValue here has a strategy of GenerationType.IDENTITY, which specifies that Hibernate will mirror the behavior of our manually created JDBC schema: the keys for each Message will be automatically generated by the database.

The @Column(nullable = false) likewise indicates that the text field can’t store a null in the database. The column name will be derived from the field name, mangled slightly if it matches a reserved word; in this case, our database is fine with a column called text so no mangling occurs, and we can provide an explicit column name if we so desire.

Apart from the annotations and the constructors, the Message and the MessageEntity are very similar.

Next, we need to look at how we tell Hibernate to connect to a database and how it should behave. We do this with a configuration file, conventionally named hibernate.cfg.xml and located in the execution classpath; in general, the files will all look the same with the exception of the JDBC URLs and the mapping references. Because this is written for a test, we’ll put it in our src/test/resources directory.
<?xml version="1.0"?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <!--  Database connection settings  -->
        <property name="connection.driver_class">org.h2.Driver</property>
        <property name="connection.url">jdbc:h2:./db1</property>
        <property name="connection.username">sa</property>
        <property name="connection.password"/>
        <property name="dialect">org.hibernate.dialect.H2Dialect</property>
        <!--  Echo all executed SQL to stdout  -->
        <property name="show_sql">true</property>
        <!--  Drop and re-create the database schema on startup  -->
        <property name="hbm2ddl.auto">create-drop</property>
        <mapping class="chapter01.hibernate.Message"/>
    </session-factory>
</hibernate-configuration>
Listing 1-8

chapter01/src/test/resources/hibernate.cfg.xml

Most of our configurations will look pretty similar to this, honestly. But what is it telling us?

connection1.driver.class

This is the fully qualified name of the JDBC driver class for the session factory.

connection.url

This is the JDBC URL used to connect to the database.

connection.username

The connection’s username, surprisingly enough.

connection.password

Another surprise – the connection’s password. In an uninitialized H2 database, “sa” and an empty password are sufficient.

dialect

This property tells Hibernate how to write SQL for the specific database.

show_sql

This property sets Hibernate to echo its generated SQL statements to a specified logger.

hbm2ddl.auto

This property tells Hibernate whether it should manage the database schema; in this case, we’re telling it to create on initialization and drop the database when it’s done.

hbm2ddl.auto is dangerous in production environments. For temporary, or testing, environments, it’s no big deal, but when you’re talking about real data that needs to be preserved, this property can be destructive, a word one rarely wants to hear when talking about valuable data.

The last line tells Hibernate that it has one entity type to manage, the chapter01.hibernate.Message class.

There’s one more configuration file to consider, although it’s optional. (It’s included in the source for the book.) The parent project specifies logback-classic as a dependency, which means that every chapter receives Logback and its transitive dependencies as classpath elements. Logback has a default configuration, but it tends to be extraordinarily noisy for our purposes. Here’s a logback.xml configuration file that trims out some of the noise.
<configuration>
  <appender
      name="STDOUT"
      class="ch.qos.logback.core.ConsoleAppender">
    <encoder
        class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
      <Pattern>
        %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n
      </Pattern>
    </encoder>
  </appender>
  <logger name="org.hibernate.SQL"
          level="debug"
          additivity="false">
    <appender-ref ref="STDOUT"/>
  </logger>
  <logger name="org.hibernate.type.descriptor.sql"
          level="trace"
          additivity="false">
    <appender-ref ref="STDOUT"/>
  </logger>
  <root level="info">
    <appender-ref ref="STDOUT"/>
  </root>
</configuration>
Listing 1-9

chapter01/src/main/resources/logback.xml

Note that the default logger level is set to info. This tends to create a lot of information on the logger’s output stream (the console); it’s interesting to look at and can be very helpful for diagnostic purposes, but if you want, you can set the logger level to error and reduce the chattiness of Hibernate quite dramatically.

Now we have all the plumbing and secondary configuration files out of the way: it’s finally time to look at what some actual Hibernate code looks like. Our test actually mirrors what the JDBC test does, almost exactly. It’s far more succinct code than the JDBC code was.
package chapter01.hibernate;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import java.util.List;
import static org.testng.Assert.assertEquals;
public class PersistenceTest {
  private SessionFactory factory = null;
  @BeforeClass
  public void setup() {
    StandardServiceRegistry registry =
        new StandardServiceRegistryBuilder()
            .configure()
            .build();
    factory = new MetadataSources(registry)
        .buildMetadata()
        .buildSessionFactory();
  }
  public Message saveMessage(String text) {
    Message message = new Message(text);
    try (Session session = factory.openSession()) {
      Transaction tx = session.beginTransaction();
      session.persist(message);
      tx.commit();
    }
    return message;
  }
  @Test
  public void readMessage() {
    Message savedMessage = saveMessage("Hello, World");
    List<Message> list;
    try (Session session = factory.openSession()) {
      list = session
          .createQuery("from Message", Message.class)
          .list();
    }
    assertEquals(list.size(), 1);
    for (Message m : list) {
      System.out.println(m);
    }
    assertEquals(list.get(0), savedMessage);
  }
}
Listing 1-10

chapter01/src/test/java/chapter01/hibernate/PersistenceTest.java

The first thing to notice is the way we’re accessing resources. In the JDBC version, we had a simple getConnection() that we called whenever we happened to need a Connection; here, we’re creating a reference to a SessionFactory and initializing it before the class’ tests run. The way we build it is …​ not complex, but it’s verbose for something we’re likely to do over and over again.12

Once we have the SessionFactory, though, the idioms are very straightforward. We create a block for which a Session is in scope – again, with automatic resource management – and then we begin a transaction. (In the JDBC example, we did the same thing, just implicitly.) Then we save() the object, or query for one, as we need.

Once we’ve done something with the database, we commit the transaction.

We’re going to discuss a lot more about the actual configuration and mapping in future chapters; it’s okay if you’re wondering what settings are available, and what operations there are, and why one would want a transaction for a read operation. We’re going to cover all of that.

If you run this code (again, with mvn test or mvn build), you might see a ton of logging output, largely because of the show_sql property being set to true in the Hibernate configuration file.

Summary

In this chapter, we have considered the problems and requirements that have driven the development of Hibernate. We have looked at some of the details of a trivial example application written with and without the aid of Hibernate. We have glossed over some of the implementation details, but we will discuss these in depth in Chapter 2.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.86.56