Статьи

NuoDB и объектно-реляционное отображение — поддержка Hibernate в NuoDB

Примечание куратора: Это вторая из трех статей, в которых рассматривается NuoDB. Вы можете узнать больше о NuoDB, а также получить бесплатную пробную версию, перейдя на сайт NuoDB .

Вступление

В  предыдущей статье мы представили NuoDB как совершенно новый подход к реляционным базам данных, который поддерживает масштабируемость, а также 100% свойства SQL и ACID. В конце статьи мы показали пример Java-программы, использующей JDBC для подключения к базе данных NuoDB.

Преимущества использования JDBC — чистая и простая обработка SQL и хорошая производительность, Тем не менее, когда речь идет об объектно-ориентированных языках, таких как Java, существует несоответствие между представлением реляционной базы данных и объектной моделью. В RDBMS мы традиционно используем строки и столбцы для представления данных, в то время как в объектно-ориентированных языках мы инкапсулировали поля данных / атрибуты, доступ к которым осуществляется через методы получения и установки. Существует несоответствие в отношении идентичности (метод первичный ключ и объекты равны ()), ассоциации (внешние ключи и ссылки на объекты) и наследования (нет наследования в СУБД в отличие от объектно-ориентированных языков, где это является общей концепцией). Именно здесь на помощь приходит Object-Relational Mapping (ORM) — это концепция программирования для преобразования данных между RDBMS и объектно-ориентированными языками. В Java есть несколько фреймворков, которые поддерживают постоянные данные. Одним из самых популярных фреймворков являетсяHibernate , который был запущен в 2001 году как альтернатива объектным компонентам J2E и стал фактическим лидером среди постоянных Java-решений.

Поддержка Hibernate в NuoDB

Hibernate имеет класс диалекта. Абстрактный класс org.hibernate.dialect.Dialect представляет собой диалект SQL, реализованный конкретной СУБД. Существует много подклассов этого абстрактного класса, таких как MySQLDialect, PostgreSQLDialect, OracleDialect, SQLServerDialect, DB2Dialect, SybaseDialect, TeradataDialect и т. Д., Которые используются для реализации совместимости Hibernate для реляционных баз данных.

NuoDB имеет свой собственный класс диалекта Hibernate под названием NuoDBDialect. Он является частью файла nuodb-hibernate-1.0.jar. Если программа хочет использовать Hibernate с NuoDB, она должна иметь этот файл .jar в пути к классам. Разрабатываемое издание NuoDB поставляется с образцом кода Hibernate из популярной книги Java Persistence with Hibernate . Полный пример можно найти здесь:  http://jpwh.org .

Чтобы запустить пример, нам нужно установить Maven. Чтобы было понятно: сам NuoDB не требует Maven; ни серверные компоненты NuoDB (механизмы транзакций или менеджеры хранилищ), ни агент NuoDB, ни менеджер баз данных NuoDB. Это исключительно необходимо для компиляции и запуска этого примера Hibernate.

Ниже приведен пример того, как попробовать NuoDB Hibernate в системе Ubuntu 12.04 LTS. Обратите внимание, что установочный файл NuoDB (nuodb-1.0.1.linux.x64.tar.gz) был установлен в домашнем каталоге пользователя с помощью следующих команд:

 $ cd
 $ tar xvzf nuodb-1.0.1.linux.x64.tar.gz
 $ ln -s nuodb-1.0.1.128.linux.x86_64 nuodb                                                                                                                                               
 $ ls -l nuodb
lrwxrwxrwx 1 notroot notroot 29 Mar 21 23:25 nuodb -> nuodb-1.0.1.128.linux.x86_64/
$ sudo apt-get install maven
$ cd ~/nuodb/samples/hibernate
# Install nuodbjdbc.jar and nuodb-hibernate-1.0.jar files into ~/.m2/repository/com/nuodb directory (local maven repository)
$ mvn install:install-file -DgroupId=com.nuodb -DartifactId=nuodb-jdbc -Dversion=1.0 -Dpackaging=jar -Dfile=/opt/nuodb/jar/nuodbjdbc.jar
$ mvn install:install-file -DgroupId=com.nuodb -DartifactId=nuodb-hibernate -Dversion=1.0 -Dpackaging=jar -Dfile=/opt/nuodb/jar/nuodb-hibernate-1.0.jar 
$ ls ~/.m2/repository/com/nuodb
nuodb-hibernate  nuodb-jdbc

# Compile the code
$ mvn compile

# Execute the java program
$ mvn exec:java
...
...
Found 2 user records:
User (3/0), Username: fred, Name: Fred Flintstone, admin
  home: Street: '301 Cobblestone Way', Zipcode: '00001', City: 'Bedrock'
  bill: Street: '301 Cobblestone Way', Zipcode: '00001', City: 'Bedrock'
  ship: Street: '1 Slate Drive', Zipcode: '00002', City: 'Granitetown'
User (4/0), Username: barney, Name: Barney Rubble, member

  home: Street: '303 Cobblestone Way', Zipcode: '00001', City: 'Bedrock'
  bill: Street: '303 Cobblestone Way', Zipcode: '00001', City: 'Bedrock'
  ship: Street: '1 Slate Drive', Zipcode: '00002', City: 'Granitetown'
Found 2 user records:
Found 2 user records:
User (3/1), Username: fred, Name: FRED FLINTSTONE, member
  home: Street: '301 Cobblestone Way', Zipcode: '00001', City: 'Bedrock'
  bill: Street: '301 COBBLESTONE WAY', Zipcode: '00001', City: 'BEDROCK'
  ship: Street: '1 Slate Drive', Zipcode: '00002', City: 'Granitetown'
User (4/1), Username: barney, Name: BARNEY RUBBLE, member

  home: Street: '303 Cobblestone Way', Zipcode: '00001', City: 'Bedrock'
  bill: Street: '303 COBBLESTONE WAY', Zipcode: '00001', City: 'BEDROCK'
  ship: Street: '1 Slate Drive', Zipcode: '00002', City: 'Granitetown'
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

Основным классом для примера кода NuoDB Hibernate является SampleMain.java. Он создает двух пользователей и распечатывает записи. Затем он вводит имя пользователя и адрес для выставления счетов и снова печатает результаты. Стоит отметить, что в коде используются транзакции Hibernate. Методы createUsers (), modifyUsers () и printUSers () полагаются на транзакции, гарантирующие, что два пользователя будут изменены в одной транзакции. Метод save () класса Hibernate Session сохраняет временный экземпляр. В случае успешных операций транзакции фиксируются, тогда как в случае исключений они откатываются.

public class SampleMain
{
  private static Logger LOG = Logger.getLogger(SampleMain.class);

  public static void main(String[] args)
  {
    runit();  
  }

  public static int runit() 
  {
    Configuration configuration = new Configuration();
    configuration.configure();
	
    String chorus = System.getProperty("nuodb.url");
	
	if (chorus != null)
		{
		System.out.println("Using JDBC URL: " + chorus);
		configuration.setProperty("hibernate.connection.url", chorus);
		}
	
    SessionFactory factory = configuration.buildSessionFactory();
    Session session = factory.openSession();
    int users = 0;
    try {
      createUsers(session);
      printUsers(session);
      modifyUsers(session);
      users = printUsers(session);
    } finally {
      session.close();
      factory.close();
    }
    return users;
  }
    
  /**
   * Create new user records using the given hibernate session.
   */
  private static void createUsers(Session session)
  {
    Transaction txn = session.beginTransaction();
    try {
      // @formatter:off
      createUser(session,
                 "Fred", "Flintstone", 
                 "fredf@example.com", "fred", true,
                 "301 Cobblestone Way", "Bedrock", "00001", 
                 "1 Slate Drive", "Granitetown", "00002");
      createUser(session,
                 "Barney", "Rubble", 
                 "barney@example.com", "barney", false,
                 "303 Cobblestone Way", "Bedrock", "00001", 
                 "1 Slate Drive", "Granitetown", "00002");
      // @formatter:on
      txn.commit();
    } finally {
      if (txn.isActive())
        txn.rollback();
    }
  }

  /**
   * Instantiate a new User instance and save it to the given session.
   */
  // @formatter:off
  private static User createUser(Session session,
                                 String firstName, String lastName, 
                                 String email, String userName, boolean admin,
                                 String homeStreet, String homeCity, String homeZip, 
                                 String shipStreet, String shipCity, String shipZip)
  // @formatter:on
  {
    User user = new User(firstName, lastName, userName, "", email);
    user.setHomeAddress(new Address(homeStreet, homeZip, homeCity));
    user.setAdmin(admin);
    // always use home address for billing
    user.setBillingAddress(new Address(homeStreet, homeZip, homeCity));
    user.setShippingAddress(new AddressEntity(shipStreet, shipZip, shipCity));
    user.getShippingAddress().setUser(user);
    session.save(user);
    LOG.info("Created user " + user);
    return user;
  }

  /**
   * Modify all Users by converting the name and billing address fields to upper
   * case. Home and shipping address are not changed.
   */
  private static void modifyUsers(Session session)
  {
    Transaction txn = session.beginTransaction();
    try {
      Query query = session.createQuery("from " + User.class.getName());

      @SuppressWarnings("unchecked")
      List<User> users = query.list();
      System.out.println("Found " + users.size() + " user records:");
      for (User user : users) {
        LOG.info("Updating " + user);
        user.setFirstname(user.getFirstname().toUpperCase());
        user.setLastname(user.getLastname().toUpperCase());
        user.setAdmin(false);
        Address bill = user.getBillingAddress();
        bill.setStreet(bill.getStreet().toUpperCase());
        bill.setCity(bill.getCity().toUpperCase());
        bill.setZipcode(bill.getZipcode().toUpperCase());
      }
      txn.commit();
    } finally {
      if (txn.isActive())
        txn.rollback();
    }
  }

  /**
   * Iterate over all User records and print them.
   */
  private static int printUsers(Session session)
  {
    int count = 0;
    Transaction txn = session.beginTransaction();
    try {
      Query query = session.createQuery("from " + User.class.getName());

      @SuppressWarnings("unchecked")
      List<User> users = query.list();
      System.out.println("Found " + users.size() + " user records:");
      for (User user : users) {
        System.out.println(user);
        System.out.println("  home: " + user.getHomeAddress());
        System.out.println("  bill: " + user.getBillingAddress());
        System.out.println("  ship: " + user.getShippingAddress());
	count++;
      }
      txn.commit();
    } finally {
      if (txn.isActive())
        txn.rollback();
    }
    return count;
  }
}

Классами модели данных являются User, Address и AddressEntity. Традиционно объектно-реляционные отображения определяются в документах XML для представления преобразования данных из простого старого объекта Java (POJO) в таблицы базы данных и наоборот. Hibernate также поддерживает аннотации, такие как @Entity, @Table, @Column для определения отображений без использования файлов XML. Исходный пример NuoDB Hibernate, который поставляется вместе с установочным пакетом, использует файлы отображения Hibernate XML, но я переписал файлы модели Java для использования аннотаций Hibernate.

Для поддержки аннотаций Hibernate потребовались следующие модификации:

Сначала мне пришлось изменить файл pom.xml, чтобы добавить hibernate-аннотации и ejb3-persistence:

   <dependencies>
   ...  
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-annotations</artifactId>
      <version>3.4.0.GA</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>ejb3-persistence</artifactId>
      <version>1.0.1.GA</version>
    </dependency>     
    ...
  </dependencies>                                                                                                                                                 

Затем я изменил три файла модели — Address.java, AddressEntity.java и User.java — чтобы использовать аннотации

После перехода из файлов отображения Hibernate в аннотации Hibernate файл Address.java выглядит следующим образом

package com.nuodb.sample.model;

import java.io.Serializable;

import javax.persistence.*;

   
@Embeddable
public class Address implements Serializable
{
  private static final long serialVersionUID = 1L;

  @Column(length = 255, nullable = false)
  private String street;

  @Column(length = 16, nullable = false)
  private String zipcode;

  @Column(length = 255, nullable = false)
  private String city;

  public Address()
  {
  }

  public Address(String street, String zipcode, String city)
  {
    this.street = street;
    this.zipcode = zipcode;
    this.city = city;
  }
...
...
}

Файл AddressEntity.java выглядит следующим образом с аннотациями Hibernate:

package com.nuodb.sample.model;

import java.io.Serializable;

import javax.persistence.*;

@org.hibernate.annotations.GenericGenerator(
    name = "userAddressSharedPKGenerator",
    strategy ="foreign",
    parameters = @org.hibernate.annotations.Parameter(name = "property", value = "user")
)

@Entity
@Table(name = "ADDRESS")
public class AddressEntity implements Serializable
{
  private static final long serialVersionUID = 1L;

  @Id @GeneratedValue(generator = "userAddressSharedPKGenerator")
  @Column(name = "ADDRESS_ID")
  private Long id = null;

  @Version
  @Column(name = "OBJ_VERSION")
  private int version = 0;

  @Column(name = "STREET", length = 255, nullable = false)
  private String street;

  @Column(name = "ZIPCODE", length = 16, nullable = false)
  private String zipcode;

  @Column(name = "CITY", length = 255, nullable = false)
  private String city;

  @OneToOne(optional = false, fetch = FetchType.LAZY)
  @PrimaryKeyJoinColumn
  private User user;

  public AddressEntity()
  {
  }                                                                                                                                                 
...
}

И файл User.java выглядит следующим образом с аннотациями Hibernate:

package com.nuodb.sample.model;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.*;

@Entity
@Table(name = "USERS")
@SecondaryTable(
    name = "BILLING_ADDRESS",
    pkJoinColumns = {
        @PrimaryKeyJoinColumn(name="USER_ID")
    }
)
@org.hibernate.annotations.BatchSize(size = 10)
public class User implements Serializable, Comparable<User>
{
  private static final long serialVersionUID = 1L;

  @Id @GeneratedValue
  @Column(name = "USER_ID")
  private Long id = null;

  @Version
  @Column(name = "OBJ_VERSION")
  private int version = 0;

  @Column(name = "FIRSTNAME", length = 255, nullable= false)
  private String firstname;

  @Column(name = "LASTNAME", length= 255, nullable= false)
  private String lastname;

  //@Column(name = "USERNAME", length = 16, nullable = false, unique = true) -- example taken from JPA book, not immutable
  @Column(name = "USERNAME", length = 16, nullable = false, unique = true, updatable = false)
  private String username; // Unique and immutable

  @Column(name = "`PASSWORD`", length = 12, nullable = false)
  private String password;

  @Column(name = "EMAIL", length = 255 , nullable = false)
  private String email;

  @Column(name = "RANK", nullable = false)
  private int ranking = 0;

  @Column(name = "IS_ADMIN", nullable = false)
  private boolean admin = false;

  @Embedded
  @AttributeOverrides( {
      @AttributeOverride(name   = "street",
                         column = @Column(name="HOME_STREET", length = 255) ),
      @AttributeOverride(name   = "zipcode",
                         column = @Column(name="HOME_ZIPCODE", length = 16) ),
      @AttributeOverride(name   = "city",
                         column = @Column(name="HOME_CITY", length = 255) )
      })
  private Address homeAddress;

  @Embedded
  @AttributeOverrides( {
      @AttributeOverride(
          name   = "street",
          column = @Column(name="STREET", length = 255,
                           table = "BILLING_ADDRESS")
      ),
      @AttributeOverride(
          name   = "zipcode",
          column = @Column(name="ZIPCODE", length = 16,
                           table = "BILLING_ADDRESS")
      ),
      @AttributeOverride(
          name   = "city",
          column = @Column(name="CITY", length = 255,
                           table = "BILLING_ADDRESS")
      )
  })
  private Address billingAddress;

  @OneToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
  @PrimaryKeyJoinColumn
  private AddressEntity shippingAddress;

  @Temporal(TemporalType.TIMESTAMP)
  @Column(name="CREATED", nullable = false, updatable = false)
  private Date created = new Date();

  public User()
  {
  }
                                                                                                                                                
...
}

Наконец, нам нужно удалить файлы hbm.xml из каталога модели. После всех этих изменений мы можем запустить команды mvn compile и mvn exec: java, как описано выше.

Если мы войдем в тестовую базу данных NuoDB, мы сможем проверить 3 таблицы, которые были созданы: Users, Address, Billing Address — каждая таблица имеет две строки:

$ bin/nuosql test --user cloud --password user
SQL> use sample
SQL> show tables;

	Tables in schema SAMPLE

		ADDRESS
		BILLING_ADDRESS
		USERS
SQL> select * from USERS;

 USER_ID  OBJ_VERSION  FIRSTNAME   LASTNAME  USERNAME  PASSWORD        EMAIL        RANK  IS_ADMIN          CREATED             HOME_STREET     HOME_ZIPCODE  HOME_CITY  
 -------- ------------ ---------- ---------- --------- --------- ------------------ ----- --------- ----------------------- ------------------- ------------- ---------- 

    11         1         FRED     FLINTSTONE  fred               fredf@example.com    0       0     2013-03-29 20:01:35.362 301 Cobblestone Way     00001      Bedrock   
    12         1         BARNEY   RUBBLE      barney             barney@example.com   0       0     2013-03-29 20:01:35.389 303 Cobblestone Way     00001      Bedrock 

SQL> select * from ADDRESS;

 ADDRESS_ID  OBJ_VERSION     STREET     ZIPCODE     CITY     
 ----------- ------------ ------------- -------- ----------- 

     11           0       1 Slate Drive  00002   Granitetown 
     12           0       1 Slate Drive  00002   Granitetown 

SQL> select * from BILLING_ADDRESS;

 USER_ID        STREET        ZIPCODE   CITY   
 -------- ------------------- -------- ------- 

    11    301 COBBLESTONE WAY  00001   BEDROCK 
    12    303 COBBLESTONE WAY  00001   BEDROCK 

Вывод

NuoDB предлагает полные возможности реляционной базы данных SQL и ACID. В современной среде РСУБД в настоящее время распространено требование иметь поддержку объектно-реляционного сопоставления. Вот как мы можем легко преодолеть разрыв между объектно-ориентированными языками и реляционными базами данных. NuoDB имеет поддержку Hibernate, которая может облегчить разработку программного обеспечения в крупномасштабном проекте разработки, где нам нужны масштабируемые реляционные базы данных с интеграцией Java.