Примечание куратора: Это вторая из трех статей, в которых рассматривается 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.