Skip to content

Commit

Permalink
doc: active record and repository patterns
Browse files Browse the repository at this point in the history
Fixes #6135 and #6945
  • Loading branch information
loicmathieu committed Feb 4, 2020
1 parent 40079ee commit 7554454
Show file tree
Hide file tree
Showing 2 changed files with 398 additions and 177 deletions.
304 changes: 214 additions & 90 deletions docs/src/main/asciidoc/hibernate-orm-panache.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,26 @@ Does this look interesting? Read on!

NOTE: the `list()` method might be surprising at first. It takes fragments of HQL (JP-QL) queries and contextualizes the rest. That makes for very concise but yet readable code.

NOTE: what you just see is the link:https://www.martinfowler.com/eaaCatalog/activeRecord.html[active record pattern], sometimes just called the entity pattern.
Hibernate with Panache also provides the more classical link:https://martinfowler.com/eaaCatalog/repository.html[repository pattern] via `PanacheRepository`.

== Solution

We recommend that you follow the instructions in the next sections and create the application step by step.
However, you can go right to the completed example.

Clone the Git repository: `git clone {quickstarts-clone-url}`, or download an {quickstarts-archive-url}[archive].

The solution is located in the `hibernate-orm-panache-quickstart` {quickstarts-tree-url}/hibernate-orm-panache-quickstart[directory].


== Setting up and configuring Hibernate ORM with Panache

To get started:

* add your settings in `{config-file}`
* annotate your entities with `@Entity` and make them extend `PanacheEntity`
* place your entity logic in static methods in your entities
* annotate your entities with `@Entity`
* make your entities extend `PanacheEntity` (optional if you are using the repository pattern)

Follow the link:hibernate-orm#setting-up-and-configuring-hibernate-orm[Hibernate set-up guide for all configuration].

Expand Down Expand Up @@ -89,7 +102,9 @@ quarkus.datasource.password = connor
quarkus.hibernate-orm.database.generation = drop-and-create
----

== Defining your entity
== Solution 1: using the active record pattern

=== Defining your entity

To define a Panache entity, simply extend `PanacheEntity`, annotate it with `@Entity` and add your
columns as public fields:
Expand Down Expand Up @@ -131,7 +146,7 @@ And thanks to our field access rewrite, when your users read `person.name` they
and similarly for field writes and the setter.
This allows for proper encapsulation at runtime as all fields calls will be replaced by the corresponding getter/setter calls.

== Most useful operations
=== Most useful operations

Once you have written your entity, here are the most common operations you will be able to do:

Expand Down Expand Up @@ -198,8 +213,194 @@ List<String> namesButEmmanuels = persons

NOTE: The `stream` methods require a transaction to work.

=== Adding entity methods

Add custom queries on your entities inside the entities themselves.
That way, you and your co-workers can find them easily, and queries are right next to the object they operate on.
Adding them as static methods in your entity class is the Panache Active Record way.

[source,java]
----
@Entity
public class Person extends PanacheEntity {
public String name;
public LocalDate birth;
public Status status;
public static Person findByName(String name){
return find("name", name).firstResult();
}
public static List<Person> findAlive(){
return list("status", Status.Alive);
}
public static void deleteStefs(){
delete("name", "Stef");
}
}
----

== Solution 2: using the repository pattern


=== Defining your entity

When using the repository pattern, you can define your entities as regular JPA entities.

[source,java]
----
@Entity
public class Person {
@Id @GeneratedValue private Long id;
private String name;
private LocalDate birth;
private Status status;
public Long getId(){
return id;
}
public void setId(Long id){
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public LocalDate getBirth() {
return birth;
}
public void setBirth(LocalDate birth) {
this.birth = birth;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
}
----

TIP: If you don't want to bother defining getters/setters for your entities, you can make them extend `PanacheEntityBase` and
Quarkus will generates them for you. You can even extend `PanacheEntity` and take advantage of the default ID it provides.

=== Defining your repository

== Paging
When using Repositories, you can get the exact same convenient methods as with the active record pattern, injected in your Repository,
by making them implements `PanacheRepository`:

[source,java]
----
@ApplicationScoped
public class PersonRepository implements PanacheRepository<Person> {
// put your custom logic here as instance methods
public Person findByName(String name){
return find("name", name).firstResult();
}
public List<Person> findAlive(){
return list("status", Status.Alive);
}
public void deleteStefs(){
delete("name", "Stef");
}
}
----

All the operations that are defined on `PanacheEntityBase` are available on your repository, so using it
is exactly the same as using the active record pattern, except you need to inject it:

[source,java]
----
@Inject
PersonRepository personRepository;
@GET
public long count(){
return personRepository.count();
}
----

=== Most useful operations

Once you have written your repository, here are the most common operations you will be able to do:

[source,java]
----
// creating a person
Person person = new Person();
person.name = "Stef";
person.birth = LocalDate.of(1910, Month.FEBRUARY, 1);
person.status = Status.Alive;
// persist it
personRepository.persist(person);
// note that once persisted, you don't need to explicitly save your entity: all
// modifications are automatically persisted on transaction commit.
// check if it's persistent
if(personRepository.isPersistent(person)){
// delete it
personRepository.delete(person);
}
// getting a list of all Person entities
List<Person> allPersons = personRepository.listAll();
// finding a specific person by ID
person = personRepository.findById(personId);
// finding a specific person by ID via an Optional
Optional<Person> optional = personRepository.findByIdOptional(personId);
person = optional.orElseThrow(() -> new NotFoundException());
// finding all living persons
List<Person> livingPersons = personRepository.list("status", Status.Alive);
// counting all persons
long countAll = personRepository.count();
// counting all living persons
long countAlive = personRepository.count("status", Status.Alive);
// delete all living persons
personRepository.delete("status", Status.Alive);
// delete all persons
personRepository.deleteAll();
// update all living persons
personRepository.update("name = 'Moral' where status = ?1", Status.Alive);
----

All `list` methods have equivalent `stream` versions.

[source,java]
----
Stream<Person> persons = personRepository.streamAll();
List<String> namesButEmmanuels = persons
.map(p -> p.name.toLowerCase() )
.filter( n -> ! "emmanuel".equals(n) )
.collect(Collectors.toList());
----

NOTE: The `stream` methods require a transaction to work.

NOTE: The rest of the documentation show usages based on the active record pattern only,
but they can be done with the repository pattern as well.
We just don't feel the need to duplicate all the documentation for both patterns.

== Advanced Query

=== Paging

You should only use `list` and `stream` methods if your table contains small enough data sets. For larger data
sets you can use the `find` method equivalents, which return a `PanacheQuery` on which you can do paging:
Expand Down Expand Up @@ -236,7 +437,7 @@ return Person.find("status", Status.Alive)

The `PanacheQuery` type has many other methods to deal with paging and returning streams.

== Sorting
=== Sorting

All methods accepting a query string also accept the following simplified query form:

Expand All @@ -257,35 +458,7 @@ List<Person> persons = Person.list("status", Sort.by("name").and("birth"), Statu

The `Sort` class has plenty of methods for adding columns and specifying sort direction.

== Adding entity methods

In general, we recommend not adding custom queries for your entities outside of the entities themselves,
to keep all model queries close to the models they operate on. So we recommend adding them as static methods
in your entity class:

[source,java]
----
@Entity
public class Person extends PanacheEntity {
public String name;
public LocalDate birth;
public Status status;
public static Person findByName(String name){
return find("name", name).firstResult();
}
public static List<Person> findAlive(){
return list("status", Status.Alive);
}
public static void deleteStefs(){
delete("name", "Stef");
}
}
----

== Simplified queries
=== Simplified queries

Normally, HQL queries are of this form: `from EntityName [where ...] [order by ...]`, with optional elements
at the end.
Expand All @@ -311,7 +484,7 @@ Order.find("select distinct o from Order o left join fetch o.lineItems");
Order.update("update from Person set name = 'Moral' where status = ?", Status.Alive);
----

== Query parameters
=== Query parameters

You can pass query parameters by index (1-based) as shown below:

Expand Down Expand Up @@ -345,55 +518,6 @@ Person.find("name = :name and status = :status",

Every query operation accepts passing parameters by index (`Object...`), or by name (`Map<String,Object>` or `Parameters`).

== The DAO/Repository option

Repository is a very popular pattern and can be very accurate for some use case, depending on
the complexity of your needs.

Whether you want to use the Entity based approach presented above or a more traditional Repository approach, it is up to you,
Panache and Quarkus have you covered either way.

If you lean towards using Repositories, you can get the exact same convenient methods injected in your Repository by making it
implement `PanacheRepository`:

[source,java]
----
@ApplicationScoped
public class PersonRepository implements PanacheRepository<Person> {
// put your custom logic here as instance methods
public Person findByName(String name){
return find("name", name).firstResult();
}
public List<Person> findAlive(){
return list("status", Status.Alive);
}
public void deleteStefs(){
delete("name", "Stef");
}
}
----

Absolutely all the operations that are defined on `PanacheEntityBase` are available on your DAO, so using it
is exactly the same except you need to inject it:

[source,java]
----
@Inject
PersonRepository personRepository;
@GET
public long count(){
return personRepository.count();
}
----

So if Repositories are your thing, you can keep doing them. Even with repositories, you can keep your entities as
subclasses of `PanacheEntity` in order to get the ID and public fields working, but you can even skip that and
go back to specifying your ID and using getters and setters if that's your thing. Use what works for you.

== Transactions

Expand Down Expand Up @@ -430,7 +554,7 @@ Panache provides direct support for database locking with your entity/repository

The following examples are for the entity pattern but the same can be used with repositories.

== First: Locking using findById().
=== First: Locking using findById().

[source,java]
----
Expand All @@ -447,7 +571,7 @@ public class PersonEndpoint {
}
----

== Second: Locking in a find().
=== Second: Locking in a find().

[source,java]
----
Expand Down Expand Up @@ -531,9 +655,9 @@ a custom ID strategy, you can extend `PanacheEntityBase` instead and handle the
- Use public fields. Get rid of dumb getter and setters. Under the hood, we will generate all getters and setters
that are missing, and rewrite every access to these fields to use the accessor methods. This way you can still
write _useful_ accessors when you need them, which will be used even though your entity users still use field accesses.
- Don't use DAOs or Repositories: put all your entity logic in static methods in your entity class. Your entity superclass
comes with lots of super useful static methods and you can add your own in your entity class. Users can just start using
your entity `Person` by typing `Person.` and getting completion for all the operations in a single place.
- With the active record pattern: put all your entity logic in static methods in your entity class and don't create DAOs.
Your entity superclass comes with lots of super useful static methods, and you can add your own in your entity class.
Users can just start using your entity `Person` by typing `Person.` and getting completion for all the operations in a single place.
- Don't write parts of the query that you don't need: write `Person.find("order by name")` or
`Person.find("name = ?1 and status = ?2", "stef", Status.Alive)` or even better
`Person.find("name", "stef")`.
Expand Down
Loading

0 comments on commit 7554454

Please sign in to comment.