В мире Java есть много способов организации связи с БД и связи моделей с БД в частности. Есть стандарт - JPA. В мире spring есть слой совместимости с JPA, есть и иные решения. В Scala можно использовать и вышеупомянутые решения, и свои (тут уже зависит от используемого фреймворка).
Опишу, как это устроено в JPA.
Сперва ты описываешь связь всего приложения с БД в файле persistence.xml. В нём ты описываешь persistence-unit'ы - единицы персистентности, связь моделей с БД. Грубо говоря, можно использовать как локальные ресурсы (RESOURCE_LOCAL - связь происходит не через сервер приложений, а сторонними усилиями, связь описывается в xml-файле), так и ресурсы JTA (соединение настраивается на сервере, в приложении ты по имени получаешь нужный DataSource).
Потом ты создаёшь классы сущностей. Создаёшь обычный (POJO) класс с аннотацией
Entity, описываешь поля, геттеры и сеттеры. Аннотациями можно настраивать всякие штуки: имя таблицы в БД, имя поля, задавать связи, тип получения (LAZY/EAGER), каскадность, автогенерацию первичных ключей и т.д.
Затем надо создать класс, который будет предоставлять интерфейс для работы с сущностями. Обычно, для каждой сущности надо сделать свой класс. Есть несколько вариантов реализации и названия этих классов. NetBeans, например, создаёт
классы-фасады: один абстрактный и по-одному фасаду, наследующий абстрактный, на каждую сущность. По ссылке всё наглядно, я думаю. Каждый фасад - это бин (аннотация
Stateless). В него инжектится EntityManager:
@PersistenceContext(unitName = "AffableBeanPU")
private EntityManager em;
При настроенной связи с БД (в persistence.xml) в em будет нужная реализация этого менеджера, через который и происходит вся магия. Плюсом является то, что все запросы автоматом используют транкзакции.
Ну а потом, в коде, надо просто инжектить этот фасад через аннотацию
EJB и использовать его (например, для реализации REST API):
import org.foo.example.entities.Foo;
import org.foo.example.facades.FooFacade;
@Path("foo")
@Consumes({"application/json", "application/xml"})
@Produces({"application/json", "application/xml"})
class FooResource {
@EJB
FooFacade facade;
@GET
public List<Foo> getAll() {
return facade.findAll();
}
@POST
public Foo create(Foo item) {
facade.create(item);
return item;
}
@GET
@Path("{id}")
public Foo getOne(@PathParam("id") Integer id) {
return facade.find(id);
}
@PUT
@Path("{id}")
public Foo update(@PathParam("id") Integer id, Foo item) {
item.setId(id);
facade.update(item);
return item;
}
@DELETE
@Path("{id}")
public void delete(@PathParam("id") Integer id) {
facade.remove(facade.find(id));
}
}
UPD.
Запилил
демо-приложение, можешь взять посмотреть.
Суть такая: ставим wildfly, добавляем пользователя. Запускаем сервер. Можно зайти в админку
127.0.0.1:9990
Там на вкладке
Configuration->Datasources будет 1 источник данных - ExampleDS. Это h2 - встроенная БД, которая крутится в данном случае в оперативке и при перезапуске сервера сбрасывается.
В файле
persistence.xml настраиваем ресурсы: указываем имя persistence-unit, и его тип - JTA. Таким образом, ничего локально настраивать не надо, приложение получает всё через ресурс, который настроен на самом сервере, по его имени (java:jboss/datasources/ExampleDS). Единственное, в конфиге указываем
<property name="hibernate.hbm2ddl.auto" value="update" />
чтобы таблицы в БД автоматом создавались (если их нет).
В пакете
entities лежат 2 сущности:
User и
Post. Обе аннотированны
Entity, таким образом, JPA может с ними работать. Ещё в сущностях присутствуют аннотации для валидации сущностей (это всякие
NotNull,
Size,
Min,
Valid и т.д.). Так же, там есть простая двусторонняя связь. В сущности
Post есть связь
ManyToOne к сущности
User, в сущности
User есть связь
OneToMany со списком постов пользователя. Последняя связь нужна, чтобы обеспечить каскадность на уровне JPA, но геттеров/сеттеров на неё нет, потому что я не хочу, чтобы этот список вылезал при получении пользователя. По-хорошему, надо её убрать, а в таблице post (которая связана с сущностью
Post) надо самому прописать каскадность при удалении, потому что пользователь особо не должен знать, что у него в зависимых.
Далее, в пакете
dao находятся классы для доступа к сущностям.
AbstractDao - это абстрактный generic-класс, в котором описаны операции для управления хранением сущностей. Все методы там тривиальны, за исключением получения.
Вообще, есть несколько способов получить сущность. Можно использовать простые SQL-запросы, можно указывать именованные запросы (
NamedQuery), которые можно описать либо через одноимённую аннотацию, либо в коде. Вторые имеют бонус в виде типобезопасности.
И ещё один из вариантов - это динамические запросы через
CriteriaBuilder. JPA генерирует метаклассы для классов, отмеченных аннотацией
Entity. Запросы можно строить, в том числе, используя эти метаклассы. Большим плюсом является то, что такие запросы можно (и нужно) делать типобезопасными (
CriteriaQuery,
TypedQuery). И IDE не ругается на приведение типов, которое было бы в случае простых нетипизированных запросов. Вообще, по
этой ссылке есть подробное описание таких типобезопасных запросов.
Особое внимание к методам
findWithLazy и
findAllWithLazy. В сущности
Post поле
owner помечено как связь
ManyToOne с ленивым типом получения (
fetch = FetchType.LAZY). Просто так такое поле получить трудно: ленивая загрузка работает только в пределах сессии: создалась сессия, запросились данные, сессия закрылась. И ленивые поля по-умолчанию не добавляются к возвращаемому объекту. Есть несколько способов побороть это. Можно убрать ленивость (
fetch = FetchType.EAGER). Можно вызвать метод
size() для поля-коллекции. Можно вручную получать поля. У меня сделано именно так. В методы
findWithLazy и
findAllWithLazy передаётся список полей, необходимых для получения. Я создаю запрос на получение корневого элемента:
Root<T> root = criteriaQuery.from(entityClass);
, а затем в цикле получаю необходимые поля:
for (String field : fields) { root.fetch(field); }
. При выполнении запроса эти поля присоединятся к результату.
В классах
UserDao и
PostDao я указываю аннотацию
Stateless для CDI и реализую абстрактный метод
getEntityManager() для получения экземпляра
PersistenceManager. Сам экземпляр я внедряю через аннотацию
PersistenceContext, где в качестве параметра
unitName я указываю имя persistence-unit, которое обозначил в
persistence.xml.
Ну и наконец, использование классов DAO в приложении (пакет
resources). Я создаю простой REST API с помощью JAX-RS. Для каждой сущности создаю по своему ресурсу, в который внедряю через аннотацию
EJB нужный DAO-класс. Там, думаю, всё очевидно.
В описании репозитория указано, как запустить и потестить всё это дело.
Надеюсь, всё понятно.