Примечание куратора: Это вторая из трех статей, в которых рассматривается 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", "[email protected]", "fred", true, "301 Cobblestone Way", "Bedrock", "00001", "1 Slate Drive", "Granitetown", "00002"); createUser(session, "Barney", "Rubble", "[email protected]", "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 [email protected] 0 0 2013-03-29 20:01:35.362 301 Cobblestone Way 00001 Bedrock 12 1 BARNEY RUBBLE barney [email protected] 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.