Chapter 1. Basics

There are two different software worlds: one is the Java world, where none other than objects are known, while the other is the relational database world, where data is king.

Java developers always work with objects that represent state and behavior modeling real-world problems. Object persistence is a fundamental requirement of Java applications. The state is modeled to be persisted in durable storage so it will be permanent.

On the other hand, when it is time to store the data, we have to rely on relational databases, where the data is traditionally represented in a row-column format with relationships and associations. Bringing Java objects to the relational world is always a challenging and complex task for Java developers. This process is often referred to as object-relational mapping (ORM).

This chapter sets the tone for our Hibernate discussion by first looking at the problem domain of object persistence. We will explore the technologies and tools, such as JDBC and Hibernate, that assist us in facing this challenge. We will compare and contrast both of these technologies and learn how Hibernate achieves object-relational-model persistence with ease and comfort.

Birth of Hibernate

Say we are designing an online banking system. We expect the bank to keep a safe copy of our accounts, personal details, preferences, and transactions. This means, in order to be durable, the application data must be persisted to a permanent storage space. In the context of this bank application, by persistent data I mean the customer, address, account, and other domain objects that we may have modeled in our design. The data that’s been persisted by our application will outlive the application itself—for example, we may have moved away from online to phone banking, but the data created by our bank application should still be visible or available if required.

So, we know now that persisting the objects (their state is the data that we need to persist) is a fundamental requirement for most real-world applications. To save the data, we need durable storage spaces called databases. There are a plethora of database vendors (such as Oracle, MySQL, DB2, JavaDB, and others) with a lengthy list of bells and whistles.

How can we persist an object graph to a database?

Enterprises employ object-oriented languages (such as Java) as their programming platforms and relational databases (such as Oracle, MySQL, Sybase, etc.) for data storage. The existence of these two software technologies is a must for most real-world applications in spite of the so-called “object-relational impedance mismatch.” We will discuss the mismatch in the next chapter in detail, but to give you an introduction, I’ll explain its main points here:

  • Inheritance is the fundamental object-oriented programming principle without which object associations would be impossible to design. Databases do not understand inheritance!
  • When it comes to the rich set of object associations like one-to-one, one-to-many, and many-to-many, databases fall flat, as they cannot support all types of relationships.
  • Lastly, there is also an identity mismatch: objects carry both an identity and equality, while database records are identified with their column values.

Developers mitigate these differences by employing various home-grown frameworks and other technical solutions and strategies.

Java has a standard tool set for accessing databases. It is called the Java Database Connectivity (JDBC) application programming interface (API). The API was very well used in Java applications until recently. While the API is well suited for small projects, it becomes quite cumbersome (and sometimes out of hand) as the domain model increases in complexity. The API also includes a lot of repetitious boilerplate code, requiring the developer to do a lot of manual coding. Furthermore, handling of the object-relational model mapping is heavy-handed too!

This was the pain point for developers: we all wished for a simple tool to persist the data without so much hassle. The Hibernate team found the gaps in the ORM mapping space and took advantage by creating a simple framework that would make the developer’s life easy.

That’s when Hibernate was born! Hibernate became an instant hit and is undeniably the most popular open source tool in the ORM tools domain. The framework was embraced overnight by the community for its simplicity and powerful features.

Problem Domain

Before we jump into exploring Hibernate in detail, let’s look at an example of the type of problem that Hibernate was invented to solve.

We all (well, at least most of us!) love watching movies. Obviously, we don’t have all the time in the world to watch those movies when they hit the screen. So we create a “wish list” of movies that we would like to watch. For this reason, we wake up one fine morning and decide to write a simple application called JustMovies! It is a web-based application that allows users to sign up for their own account to create their movie wish list. They can return to the website any time to add, modify, or delete the movies in their wish list. As we have to store the list of each user, it is imperative that we store this wish list in durable storage such as a database.

Let’s first create a simple Java application that would store and retrieve movies from a database.

MovieManager Application

Consider a Java application called MovieManager whose main job is to save, find, and retrieve the movies from a database. In addition to coding the Java application, we need a database table to store the movie information. This MOVIES table will store the data about movies as rows, as shown in Table 1-1.

Table 1-1. MOVIES
IDTITLEDIRECTORSYNOPSIS

1

Top Gun

Tony Scott

When Maverick encounters a pair of MiGs…

2

Jaws

Steven Spielberg

A tale of a white shark!

Each row will represent a Movie instance in our VanillaMovieManager application.

Let’s pretend we live in a world without Hibernate. We’ll write some sample code using JDBC that will, hopefully, meet our requirements.

Using JDBC

The first step in any database application is to establish a connection with the database. A connection is a gateway to the database for carrying out the operations on the data from a Java application. JDBC provides a connection API to create connections based on the database properties that we provide. Database providers typically implement a class that holds the database connection mechanism—for example, for the MySQL database, this class is com.mysql.jdbc.Driver and for the JavaDB (derby) database it’s org.apache.derby.jdbc.EmbeddedDriver.

Note

Note that we use the MySQL database throughout the book. Refer to Setting Up Hibernate for details on how to set up the project and database.

The createConnection method, shown in the following snippet, demonstrates the procedure to create a database connection:

public class VanillaMovieManager {
  private Connection connection = null;

  // Database properties
  private String url = "jdbc:mysql://localhost:3307/JH";
  private String driverClass = "com.mysql.jdbc.Driver";
  private String username = "mkonda";
  private String password = "mypass";
  ...
  private Connection getConnection() {
    try {
          Class.forName(driverClass).newInstance();
          connection = DriverManager.getConnection(url, username, password);
        } catch (Exception ex) {
          System.err.println("Exception:"+ ex.getMessage());
        }
        return connection;
  }
}

In this snippet, we first instantiate the driver class, and then get a connection using the DriverManager.

Once we have established our connection to the database successfully, our next step is to write a method to persist and query a Movie entity. Most parts of the methods should be familiar if you have had any experience with JDBC code.

Continuing with our application development, let’s add a couple of methods that will save the movies to and retrieve them from the database. We call these methods as persistMovie and queryMovies, respectively. The implementation of these methods is shown in the following code listing:

public class VanillaMovieManager {
  private String insertSql = "INSERT INTO MOVIES VALUES (?,?,?,?)";
  private String selectSql = "SELECT * FROM MOVIES";
  ...
  private void persistMovie() {
    try {
          PreparedStatement pst = getConnection().prepareStatement(insertSql);
          pst.setInt(1, 1001);
          pst.setString(2, "Top Gun");
          pst.setString(3, "Action Film");
          pst.setString(4, "Tony Scott");

          // Execute the statement
      pst.execute();
          System.out.println("Movie persisted successfully!");

        } catch (SQLException ex) {
          System.err.println(ex.getMessage());
          ex.printStackTrace();
        }
  }
  private void queryMovies() {
    try {
      Statement st = getConnection().createStatement();
      ResultSet rs = st.executeQuery("SELECT * FROM MOVIES");
      while (rs.next()) {
        System.out.println("Movie Found: "
                  + rs.getInt("ID")
                  + ", Title:"
                  + rs.getString("TITLE"));
      }
    } catch (SQLException ex) {
       System.err.println(ex.getMessage());
      }
  }
}

This is what we’ve done in the preceding example code:

  1. We’ve created a PreparedStatement from the connection with the insertSql string.
  2. We’ve set the statement with values (column values against column numbers: 1 is ID, 2 is title, etc.).
  3. We’ve executed the statement that should insert the movie in the table.
  4. We’ve queried the database for all Movies and printed them out to the console.

The steps are pretty much self-explanatory. We create a PreparedStatement and execute it after setting the appropriate values on it for each column. Once we know that the execution worked, we query the database with a SELECT statement in order to fetch all the movies available and print them to the console.

However, there are a few things to note:

  • We use a predefined SQL statement to insert (or select) column values.
  • We set the column values one by one using the position number (or column name).
  • We catch the SQLException if the code misbehaves.

For simple programs, this way of creating the statements with the values and executing them is fine. However, the kinds of programs we have to deal with in the real world are much more complex. JDBC will work, if you are willing and able to write and manage a great deal of nonbusiness code. Also, using JDBC might pose a challenge when you have a lot of tables or complex relationships between the objects. We are not dealing with the data in any shape or form using our favorite object orientation principles!

Improvising the Movie Application

Wouldn’t it be ideal to call a method such as persist() on a utility class so that the Movie object is persisted straightaway? After all, we are object-oriented programmers; wishing for this facility is not a sin!

To achieve this goal, we will create a plain old Java object (POJO) representing a movie. For every celluloid movie that’s released (or yet to be released), we’ll have a new Movie object created. The Movie POJO is defined here:

public class Movie {
  private int id = 0;
  private String  title = null;
  private String  synopsis = null;
  private String  director = null;
  ...
  // Setters and getters omitted
}

So, all we need now is for a facility to persist this POJO object into our database table MOVIES—in essence converting the object model (Movie object) to a relational model (table row).

Let’s create a MoviePersistor class that might do this job:

// Pseudocode
public class MoviePersistor {
  public void persist(Movie movie) {
        // Persisting a movie goes here
  }
  public void fetch(String title) {
        // Fetching a movie by title goes here
  }
  ...
}

We haven’t written the persist or fetch functionality yet; that’s not the theme of the program. Now let’s see how easy it is to persist any Movie using the MoviePersistor utility class, as demonstrated in this sample test:

  //Pseudocode
  MoviePersistor moviePersistor = new MoviePersistor();
  Movie movie = new Movie();
  movie.setId(1);
  movie.setTitle("Jaws");
  movie.setDirector("Steven Spielberg");
  movie.setSynopsis("Story of a great white shark!");

  moviePersistor.persist(movie);

How cool is that? A POJO representing a celluloid movie is persisted as a row of records into a database table—object model to relational model—via a utility class!

That’s all good, except for the actual persist and fetch method implementations. To implement this functionality, we not only need the connection facility to a database, we also need a mechanism to convert the object to a row (such as mapping our object properties to database columns).

We could write our own framework of classes to hide the nitty-gritty of these conversions and persistence mechanisms (which may use good old JDBC statements behind the scenes). Although writing this framework isn’t rocket science, it would be a time-consuming and cumbersome effort to begin with.

Over time, an organization’s persistence requirements may change or it may even migrate the database from, for example, Oracle to MySQL. This means the framework would have to be very generic and account for a plethora of functional and technical requirements before hitting the ground.

In my experience, such homegrown frameworks are unmanageable, inflexible, unscalable, and sometimes out of date too! Unless the requirement is really dead-specific to an organization (your organization may want to persist data to Mars!), I would strongly recommend that you search on the Internet to choose one that closely satisfies our predicates.

But before you go on your way to start writing this code, let me be the bearer of some good news (if you haven’t already have heard this): there’s already a great framework that does exactly this—object persistence to a relational database—called Hibernate!

Now that we’ve got a persistence framework, let’s see how the same method that persists our movie can be refactored through Hibernate:

public class BasicMovieManager {
  private void persistMovie(Movie movie) {
        Session session = sessionFactory.getCurrentSession();
        ...
        session.save(movie);
  }
  ...
}

Did you notice that we saved the Movie instance to a database by a executing a single line of code: session.save(movie)? Isn’t this what we had wished for earlier—a class that would simply save the persistent objects in an object-oriented way? Hibernate’s API classes expose several methods to manipulate the Java objects with ease and comfort. We neither have to write reams of code using JDBC nor fold up our sleeves and write a framework while scratching our heads and gulping gallons of caffeine!

Hibernate does provide the capability of object persistence; however, there is a one-off configuration and mapping we need to let Hibernate know our intentions. We delve into these details at a very high level in next couple of sections.

Using Hibernate

The standard steps to follow in creating a Hibernate application are:

  1. Configure the database connection.
  2. Create mapping definitions.
  3. Persist the classes.

Here are the common steps involved in developing the Java-Hibernate version of our MovieManager application:

  1. Create a Movie domain object (domain model POJOs representing data tables).
  2. Create configuration files such as Hibernate properties and mapping files.
  3. Create a test client that manages (insert/update/delete/find) the Movies.

We have already prepared a Movie POJO, as shown in prior snippets, so we don’t have to go over it again.

The heart of any Hibernate application is in its configuration. There are two pieces of configuration required in any Hibernate application: one creates the database connections, and the other creates the object-to-table mapping. As with JDBC, we need to provide the database information to our application so it will open up a connection for manipulating the data. The mapping configuration defines which object properties are mapped to which columns of the table. We are not going to go into detail about them here, as the aim of this chapter is to get you started quickly!

Let’s look at the standard steps for creating a Hibernate application in the following sections.

Configure the Database Connection

To create a connection to the database, Hibernate must know the details of our database, tables, classes, and other mechanics. This information is ideally provided as an XML file (usually named hibernate.cfg.xml) or as a simple text file with name/value pairs (usually named hibernate.properties).

For this exercise, we use XML style. We name this file hibernate.cfg.xml so the framework can load this file automatically.

The following snippet describes such a configuration file. Because I am using MySQL as the database, the connection details for the MySQL database are declared in this hibernate.cfg.xml file:

<hibernate-configuration>
  <session-factory>
    <property name="connection.url">
          jdbc:mysql://localhost:3307/JH
        </property>
    <property name="connection.driver_class">
          com.mysql.jdbc.Driver
        </property>
    <property name="connection.username">
          mkonda
        </property>
    <property name="connection.password">
          password
        </property>
    <property name="dialect">
          org.hibernate.dialect.MySQL5Dialect
        </property>
    <mapping resource="Movie.hbm.xml" />
  </session-factory>
</hibernate-configuration>

This file has enough information to get a live connection to a MySQL database.

The preceding properties can also be expressed as name/value pairs. For example, here’s the same information represented as name/value pairs in a text file titled hibernate.properties:

hibernate.connection.driver_class = com.mysql.jdbc.Driver
hibernate.connection.url = jdbc:mysql://localhost:3307/JH
hibernate.dialect = org.hibernate.dialect.MySQL5Dialect

connection.url indicates the URL to which we should be connected, driver_class represents the relevant Driver class to make a connection, and the dialect indicates which database dialect we are using (MySQL, in this case).

If you are following the hibernate.properties file approach, note that all the properties are prefixed with “hibernate” and follow a pattern—hibernate.* properties, for instance.

Beyond providing the configuration properties, we also have to provide mapping files and their locations. As mentioned earlier, a mapping file indicates the mapping of object properties to the row column values. This mapping is done in a separate file, usually suffixed with .hbm.xml. We must let Hibernate know our mapping definition files by including an element mapping property in the previous config file, as shown here:

<hibernate-configuration>
  <session-factory>
    ...
    <mapping resource="Movie.hbm.xml" />
        <mapping resource="Account.hbm.xml" />
        <mapping resource="Trade.hbm.xml" />
  </session-factory>
</hibernate-configuration>

The resource attribute indicates the name of the mapping resource that Hibernate should load. In this case, Movie.hbm.xml is the mapping file and consists of details on how a Movie object is mapped to a MOVIE table. You can see others as well, such as Account.hbm.xml and Trade.hbm.xml. We will look at these mapping files in a minute.

What does Hibernate do with this properties file?

The Hibernate framework loads this file to create a SessionFactory, which is a thread-safe global factory class for creating Sessions. We should ideally create a single SessionFactory and share it across the application. Note that a SessionFactory is defined for one, and only one, database. For instance, if you have another database alongside MySQL, you should define the relevant configuration in hibernate.hbm.xml to create a separate SessionFactory for that database too.

The goal of the SessionFactory is to create Session objects. Session is a gateway to our database. It is the Session’s job to take care of all database operations such as saving, loading, and retrieving records from relevant tables. The framework also maintains a transactional medium around our application. The operations involving the database access are wrapped up in a single unit of work called a transaction. So, all the operations in that transaction are either successful or rolled back.

Keep in mind that the configuration is used to create a Session via a SessionFactory instance. Before we move on, note that Session objects are not thread-safe and therefore should not be shared across different classes. We will see the details of how they should be used as we progress in this book.

Create Mapping Definitions

Once we have the connection configuration ready, the next step is to prepare the Movie.hbm.xml file consisting of object-table mapping definitions. The following XML snippet defines mapping for our Movie object against the MOVIES table:

<hibernate-mapping>
  <class name="com.madhusudhan.jh.domain.Movie" table="MOVIES">
    <id name="id" column="ID">
          <generator class="native"/>
        </id>
        <property name="title" column="TITLE"/>
        <property name="director" column="DIRECTOR"/>
        <property name="synopsis" column="SYNOPSIS"/>
  </class>
</hibernate-mapping>

There is a lot going on in this mapping file. The hibernate-mapping element holds all the class-to-table mapping definitions. Individual mappings per object are declared under the class element. The name attribute of the class tag refers to our POJO domain class com.madhusudhan.jh.domain.Movie, while the table attribute refers to the table MOVIES to which the objects are persisted.

The remaining properties indicate the mapping from the object’s variables to the table’s columns (e.g., the id is mapped to ID, the title to TITLE, director to DIRECTOR, etc.). Each object must have a unique identifier—similar to a primary key on the table. We set this identifier by implementing the id tag using a native strategy. Don’t pay too much attention to this id and the generation strategy yet. We will discuss them in detail in the coming chapters.

Persist the Objects

Now that the configuration is out of our way, let’s create a client that persists the objects with the help of Hibernate.

We need the SessionFactory instance from which we create a Session object. The following snippet shows the initial setup for creating the SessionFactory class:

public class BasicMovieManager {
  private SessionFactory sessionFactory = null;

  // Creating SessionFactory using 4.2 version of Hibernate
  private void initSessionFactory(){
   Configuration config = new Configuration().configure();
    // Build a Registry with our configuration properties
   ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(
        config.getProperties()).buildServiceRegistry();

    // create the session factory
   sessionFactory = config.buildSessionFactory(serviceRegistry);
  }
  ...
}

Note that we don’t have to explicitly mention the mapping or configuration or properties files, because the Hibernate runtime looks for default filenames, such as hibernate.cfg.xml or hibernate.properties, in the classpath and loads them. If we have a nondefault name, make sure you pass that as an argument—like configure("my-hib-cfg.xml"), for example.

The preceding settings for initializing the SessionFactory class are for the latest Hibernate version at the time of this writing, 4.2. The Hibernate 4.x version introduced service registries, which we will see in later chapters.

Note

In 3.x versions, the configure method rummages through the classpath looking for a file named hibernate.cfg.xml (or hibernate.properties) to create a Configuration object. This configuration object is then used to create a SessionFactory instance. If you are using a pre-4.x version of Hibernate, use the following code to initialize the SessionFactory:

  //Creating SessionFactory using 3.x version of Hibernate
private void init3x(){
  sessionFactory =
    new Configuration().configure().buildSessionFactory();
}

In 4.x versions, this is slightly modified by the introduction ServiceRegistry, which takes a Map of properties that can be fed from a Configuration object, as just shown.

Whichever version you choose, the SessionFactory thus created is the same and so are the Sessions.

Creating the Persist Method

Now on to the actual workings of the BasicMovieManager class. The persist method is defined on the class to persist a movie using Session’s save method. This is shown in the following snippet:

public class BasicMovieManager {
  private void persistMovie(Movie movie) {
        Session session = sessionFactory.getCurrentSession();
        session.beginTransaction();
        session.save(movie);
        session.getTransaction().commit();
  }
}

It looks simple, doesn’t it? We did not write unnecessary or repetitious code at all, but concentrated on the business function of saving the object.

In the preceding snippet, we first grab a Session from the factory. We then create a transaction object (we’ll learn more about transactions in later chapters), and persist the incoming Movie object using the session.save method. Finally, we commit the transaction, and the Movie is stored permanently in our database.

Testing the Persisted Data

We have two ways of testing the persisted data: running a native SQL query on the database, or creating a test client.

We can run the SQL query on the database using something like SELECT * FROM MOVIES, which fetches all the records stored by our application.

The SQL select query prints the output as shown in Table 1-2.

Table 1-2. MOVIES
IDTITLEDIRECTORSYNOPSIS

1

Top Gun

Tony Scott

Maverick is a hot pilot…

2

Jaws

Steven Spielberg

A tale of a white shark!

Alternatively, we can create another method in our test client, say findMovie. This method will use the Session’s load method to fetch the record from the database. We invoke the findMovie method, passing the movie ID as the argument, to fetch the movie:

public class BasicMovieManager {
  ...
  private void findMovie(int movieId) {
        Session session = sessionFactory.getCurrentSession();
        session.beginTransaction();

        Movie movie = (Movie)session.load(Movie.class, movieId);

        System.out.println("Movie:"+movie);
        session.getTransaction().commit();
  }
}

The load method on the Session API fetches the appropriate Movie object for a given identifier. If you are thinking that Hibernate may use a SELECT statement behind the scenes, you are correct!

Should you wish to fetch all movies from the table, you create a Query object with the simple query string "from Movie" and execute it. The list method on the query (created via session.createQuery) returns a List of movies, as shown here:

public class BasicMovieManager {
  // Finding all movies
  private void findAll() {
    Session session = sessionFactory.getCurrentSession();
        session.beginTransaction();
        List<Movie> movies = session.createQuery("from Movie").list();*
        session.getTransaction().commit();
    System.out.println("All Movies:"+movies);
  }
  ...
}

Setting Up Hibernate

Setting up a Hibernate project is easy. The project that I’ve prepared for this book is a Maven-based code developed on NetBeans IDE. I won’t be going into detail on setting up the environment, but the following steps should help you. Although I have used NetBeans for developing code, you can use any of your favorite IDEs to work on this project. Also, you can swap MySQL with any other databases, including in-memory ones such as Derby or HyperSQL.

First, you should set up the essential development environment consisting of JDK 5.0+, NetBeans IDE, Maven, and the MySQL database (you may have had this environment already!). I have used JDK 6, NetBeans 7.3, Maven 3.2, and MySQL 5.2 for developing the code, and the Hibernate version is 4.2.3.Final.

Once you have the dev environment sorted, the next step is to create a Maven project in NetBeans (or Eclipse). Add the appropriate Hibernate dependencies to the pom.xml file as shown here:

<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.madhusudhan</groupId>
  <artifactId>just-hibernate</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <dependencies>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-core</artifactId>
      <version>4.2.3.Final</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.18</version>
    </dependency>
   ...
</project>

Maven will resolve the related dependencies when building the project. I recommend downloading the book’s source code and importing the project into your favorite IDE.

The next step is setting up your database. If you already have a database, you can skip this step. I am using MySQL database. Download the latest MySQL package and install it on your machine (you may have to go through the manual to set this up correctly). You can try out the examples in this book using any other databases.

Once you have MySQL (or any other database) installed, make sure you create a schema named JH, as shown here:

CREATE SCHEMA JH;

Most of the tables in this project were autogenerated by Hibernate; that is, Hibernate would create them on the fly (if they do not exist) by reading your configuration. We need to set a property, hbm2ddl.auto, in the Hibernate configuration file (hibernate.cfg.xml) to the appropriate value for this autogeneration to happen, as follows:

<property name="hbm2ddl.auto">update</property>

When we set this property, the tables are automatically either created if they do not exist or updated if there’s a change in the table schema.

Warning

Never use the hbm2ddl.auto property in production! You must create a schema with all the table definitions and deploy to production via a proper release process.

That’s pretty much it!

We wished for a mechanism that hides away the nitty-gritty of clumsy JDBC statements and connections. We dreamed of creating facility methods that would store a POJO object directly to the database without the hassle of setting/getting the columns. We’ve fulfilled our dreams and wishes by embracing Hibernate!

You may have lots of questions, but they’ll be demystified as we go through our journey, so stay tuned in!

Summary

In this chapter, we learned about the object-relational-model problem domain by walking through an example. Although we can use JDBC for data access, we found that it requires a lot of manual mapping and unnecessarily repetitive code. We took a small step and introduced Hibernate to solve the problem of object-to-relational data persistence. From a high level, we took a look at the Hibernate concepts of SessionFactory and Sessions. We refactored the JDBC example to use the Hibernate framework and successfully persisted and queried the POJOs as expected.

In the next chapter, we’ll go through the fundamentals of Hibernate in detail.

Get Just Hibernate now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.