Персистират данни с Java Data Objects, част 1

„Всичко трябва да бъде възможно най-опростено, но не и по-просто.“

Алберт Айнщайн

Необходимостта от запазване на данните, създадени по време на изпълнение, е толкова стара, колкото и изчисленията. И необходимостта от съхраняване на обектно-ориентирани данни, изникнали, когато обектно-ориентираното програмиране стана широко разпространено. В момента повечето модерни, нетривиални приложения използват обектно-ориентирана парадигма за моделиране на домейни на приложения. За разлика от това пазарът на бази данни е по-разделен. Повечето системи за бази данни използват релационния модел, но обектно-базирани хранилища за данни се оказват необходими в много приложения. Освен това имаме и стари системи, с които често се налага да се свързваме.

Тази статия идентифицира проблемите, свързани с постоянството на данните в транзакционни среди на средата, като J2EE (Java 2 Platform, Enterprise Edition), и показва как Java Data Objects (JDO) решава някои от тези проблеми. Тази статия предоставя общ преглед, а не подробен урок и е написана от гледна точка на разработчик на приложения, а не на дизайнер за внедряване на JDO.

Прочетете цялата поредица за Java Data Objects:

  • Част 1. Разберете качествата зад идеалния слой на устойчивост
  • Част 2. Sun JDO срещу Castor JDO

Тези разработчици на Java, дизайнери и архитекти на J2EE, които работят върху системи, които трябва да съхраняват данни в релационни или обектни бази данни или други носители за съхранение, трябва да прочетат тази статия. Предполагам, че имате основни познания по Java и известни познания по обектно-релационните проблеми и терминология.

Прозрачна упоритост: Защо да си правим труда?

Повече от десетилетие на непрекъснати опити за преодоляване на обектно-ориентирано време на изпълнение и постоянство сочат към няколко важни наблюдения (изброени по важност):

  1. Абстрахирането на всякакви подробности за устойчивостта и наличието на чист, прост, обектно-ориентиран API за извършване на съхранение на данни е от първостепенно значение. Не искаме да обработваме подробности за постоянството и вътрешно представяне на данни в хранилищата за данни, независимо дали са релационни, обектно базирани или нещо друго. Защо трябва да се занимаваме с конструкции от ниско ниво на модела за съхранение на данни, като редове и колони, и непрекъснато да ги превеждаме напред-назад? Вместо това трябва да се концентрираме върху онова сложно приложение, което трябваше да доставим до вчера.
  2. Искаме да използваме подхода plug-and-play с нашите хранилища за данни: Искаме да използваме различни доставчици / внедрения, без да променяме ред на изходния код на приложението - и може би без да променяме повече от няколко реда в съответния конфигурационен файл ( с). С други думи, ние се нуждаем от индустриален стандарт за достъп до данни, базиран на Java обекти, който играе роля, подобна на тази, която JDBC (Java Database Connectivity) играе като индустриален стандарт за достъп до базирани на SQL данни.
  3. Искаме да използваме подхода plug-and-play с различни парадигми на базата данни - тоест искаме да преминем от релационна база данни към обектно-ориентирана с минимални промени в кода на приложението. Макар и приятно да се има, на практика тази способност често не се изисква.

    Един коментар тук: Докато релационните бази данни се радват на най-голямото присъствие на пазара досега, предоставянето на унифициран API за постоянство и позволяването на доставчиците на хранилища за данни да се конкурират по силните страни на внедряване е логично, независимо от парадигмата, която тези доставчици използват. Този подход в крайна сметка може да помогне за изравняване на условията между двете доминиращи групи доставчици на бази данни: добре укрепеният релационен лагер и обектно ориентирания лагер, който се бори за пазарен дял.

Изброените по-горе три открития ни карат да дефинираме слой за устойчивост, рамка, която осигурява Java API на високо ниво за обекти и взаимоотношения, за да надживее живота на средата на изпълнение (JVM). Такава рамка трябва да включва следните качества:

  • Простота
  • Минимално проникване
  • Прозрачност, което означава, че рамката крие внедряването на хранилището на данни
  • Последователни, кратки API за съхранение / извличане / актуализиране на обекти
  • Поддръжка на транзакции, което означава, че рамката определя транзакционната семантика, свързана с постоянни обекти
  • Поддръжка както за управлявани (например базирани на сървър на приложения), така и за неуправлявани (самостоятелни) среди
  • Поддръжка за необходимите екстри, като кеширане, заявки, инструменти за генериране на първичен ключ и инструменти за картографиране
  • Разумни лицензионни такси - не е техническо изискване, но всички знаем, че лошата икономика може да обрече отличен проект

По-подробно описвам повечето от горните качества в следващите раздели.

Простота

Опростеността е висока в списъка ми с необходими черти за всяка софтуерна рамка или библиотека (вижте началния цитат на тази статия). Разработването на разпределени приложения вече е достатъчно трудно и много софтуерни проекти се провалят поради лоша сложност (и в крайна сметка риск). Simple не е синоним на опростен; софтуерът трябва да има всички необходими функции, които позволяват на разработчика да си върши работата.

Минимално проникване

Всяка постоянна система за съхранение въвежда известно количество проникване в кода на приложението. Идеалният слой за устойчивост трябва да сведе до минимум проникването, за да се постигне по-добра модулност и по този начин функционалност plug-and-play.

За целите на тази статия определям проникването като:

  • Количеството специфичен за устойчивост код, пръскан в кода на приложението
  • Необходимостта от модифициране на обектния модел на вашето приложение, като се налага да се приложи някакъв интерфейс за постоянство - като Persistableили подобен - или чрез последваща обработка на генерирания код

Нахлуването се отнася и за обектно-ориентирани системи от бази данни и, макар че обикновено това е по-малък проблем в сравнение с релационните хранилища на данни, то може да варира значително при доставчиците на ODBMS (обектно-ориентирана система за управление на база данни).

Прозрачност

Концепцията за прозрачност на постоянния слой е доста проста: приложението използва един и същ API, независимо от типа на хранилището на данни (прозрачност на типа на съхранение на данни) или доставчика на хранилището на данни (прозрачност на доставчика на съхранение на данни). Прозрачността значително опростява приложенията и подобрява тяхната поддръжка, като скрива в максимална степен подробностите за изпълнението на хранилището на данни. По-специално, за преобладаващите хранилища на релационни данни, за разлика от JDBC, не е нужно да кодирате SQL изрази или имена на колони или да запомните реда на колоните, върнат от заявка. Всъщност не е нужно да знаете SQL или релационна алгебра, защото те са твърде специфични за изпълнението. Прозрачността е може би най-важната черта на устойчивостта.

Последователен, прост API

API за устойчивост се свежда до относително малък набор от операции:

  • Елементарни CRUD (създаване, четене, актуализиране, изтриване) операции върху първокласни обекти
  • Управление на транзакции
  • Управление на идентичности на приложения и постоянни обекти
  • Управление на кеша (т.е. опресняване и изваждане)
  • Създаване и изпълнение на заявка

Пример за PersistenceLayerAPI:

публична празнота продължава (Object obj); // Запазване на obj в хранилището за данни. публично натоварване на обект (клас c, обект pK); // Чете obj с даден първичен ключ. публична актуализация на невалидни (обект obj); // Актуализиране на модифицирания обект obj. public void delete (Object obj); // Изтриване на obj от базата данни. публична колекция намиране (Запитване q); // Намерете обекти, които отговарят на условията на нашата заявка.

Поддръжка на транзакции

Един добър устойчив слой се нуждае от няколко елементарни функции, за да стартира, ангажира или върне транзакция. Ето пример:

// Transaction (tx) demarcation. public void startTx(); public void commitTx(); public void rollbackTx(); // Choose to make a persistent object transient after all. public void makeTransient(Object o) 

Note: Transaction demarcation APIs are primarily used in nonmanaged environments. In managed environments, the built-in transaction manager often assumes this functionality.

Managed environments support

Managed environments, such as J2EE application servers, have grown popular with developers. Who wants to write middle tiers from scratch these days when we have excellent application servers available? A decent persistence layer should be able to work within any major application server's EJB (Enterprise JavaBean) container and synchronize with its services, such as JNDI (Java Naming and Directory Interface) and transaction management.

Queries

The API should be able to issue arbitrary queries for data searches. It should include a flexible and powerful, but easy-to-use, language -- the API should use Java objects, not SQL tables or other data-store representations as formal query parameters.

Cache management

Cache management can do wonders for application performance. A sound persistence layer should provide full data caching as well as appropriate APIs to set the desired behavior, such as locking levels, eviction policies, lazy loading, and distributed caching support.

Primary key generation

Providing automatic identity generation for data is one of the most common persistence services. Every decent persistence layer should provide identity generation, with support for all major primary key-generation algorithms. Primary key generation is a well-researched issue and numerous primary key algorithms exist.

Mapping, for relational databases only

With relational databases, a data mapping issue arises: the need to translate objects into tables, and to translate relationships, such as dependencies and references, into additional columns or tables. This is a nontrivial problem in itself, especially with complex object models. The topic of object-relational model impedance mismatch reaches beyond this article's scope, but is well publicized. See Resources for more information.

The following list of extras related to mapping and/or relational data stores are not required in the persistence layer, but they make a developer's life much easier:

  • A GUI (graphical user interface) mapping tool
  • Code generators: Autogeneration of DDL (data description language) to create database tables, or autogeneration of Java code and mapping files from DDL
  • Primary key generators: Supporting multiple key-generation algorithms, such as UUID, HIGH-LOW, and SEQUENCE
  • Support for binary large objects (BLOBs) and character-based large objects (CLOBs)
  • Self-referential relations: An object of type Bar referencing another object of type Bar, for example
  • Raw SQL support: Pass-through SQL queries

Example

The following code snippet shows how to use the persistence layer API. Suppose we have the following domain model: A company has one or more locations, and each location has one or more users. The following could be an example application's code:

PersistenceManager pm =PMFactory.initialize(..); Company co = new Company("MyCompany"); Location l1 = new Location1 ("Boston"); Location l2 = new Location("New York"); // Create users. User u1 = new User("Mark"); User u2 = new User("Tom"); User u3 = new User("Mary"); // Add users. A user can only "belong" to one location. L1.addUser(u1); L1.addUser(u2); L2.addUser(u3); // Add locations to the company. co.addLocation(l1); co.addLocation(l2); // And finally, store the whole tree to the database. pm.persist(c); 

In another session, you can look up companies employing the user Tom:

PersistenceManager pm =PMFactory.initialize(...) Collection companiesEmployingToms = pm.find("company.location.user.name = 'Tom'"); 

За релационни хранилища на данни трябва да създадете допълнителен файл за картографиране. Може да изглежда така:

    Местоположение на компанията Потребител             

Постоянният слой се грижи за останалото, което включва следното:

  • Намиране на зависими обектни групи
  • Управление на идентичността на обект на приложение
  • Управление на постоянни идентичности на обекти (първични ключове)
  • Запазване на всеки обект в подходящия ред
  • Осигуряване на управление на кеша
  • Осигуряване на правилния транзакционен контекст (не искаме да запази само част от дървото на обектите, нали?)
  • Предоставяне на избираеми от потребителя режими на заключване