diff --git a/docs/src/main/asciidoc/hibernate-orm-panache.adoc b/docs/src/main/asciidoc/hibernate-orm-panache.adoc index dbd992ed01540..d8206a233df96 100644 --- a/docs/src/main/asciidoc/hibernate-orm-panache.adoc +++ b/docs/src/main/asciidoc/hibernate-orm-panache.adoc @@ -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]. @@ -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: @@ -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: @@ -198,8 +213,154 @@ List 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 findAlive(){ + return list("status", Status.Alive); + } + + public static void deleteStefs(){ + delete("name", "Stef"); + } +} +---- + +== Solution 2: using the repository pattern + +=== Defining your repository + +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 { + + // put your custom logic here as instance methods + + public Person findByName(String name){ + return find("name", name).firstResult(); + } + + public List 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(); +} +---- + +Even with repositories, you can keep your entities as subclasses of `PanacheEntity` in order to get the ID and public fields working. +If you don't, you need to specify the ID yourself, and use getters and setters. +Use what works for you. + +=== 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 allPersons = personRepository.listAll(); + +// finding a specific person by ID +person = personRepository.findById(personId); + +// finding a specific person by ID via an Optional +Optional optional = personRepository.findByIdOptional(personId); +person = optional.orElseThrow(() -> new NotFoundException()); + +// finding all living persons +List 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 persons = personRepository.streamAll(); +List 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. -== Paging +== 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: @@ -236,7 +397,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: @@ -257,35 +418,7 @@ List 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 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. @@ -311,7 +444,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: @@ -345,55 +478,6 @@ Person.find("name = :name and status = :status", Every query operation accepts passing parameters by index (`Object...`), or by name (`Map` 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 { - - // put your custom logic here as instance methods - - public Person findByName(String name){ - return find("name", name).firstResult(); - } - - public List 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 @@ -430,7 +514,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] ---- @@ -447,7 +531,7 @@ public class PersonEndpoint { } ---- -== Second: Locking in a find(). +=== Second: Locking in a find(). [source,java] ---- @@ -531,9 +615,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")`. diff --git a/docs/src/main/asciidoc/mongodb-panache.adoc b/docs/src/main/asciidoc/mongodb-panache.adoc index dedc5967753ad..21c28a5a3bc6f 100644 --- a/docs/src/main/asciidoc/mongodb-panache.adoc +++ b/docs/src/main/asciidoc/mongodb-panache.adoc @@ -50,6 +50,9 @@ NOTE: the `list()` method might be surprising at first. It takes fragments of Pa That makes for very concise but yet readable code. MongoDB native queries are also supported. +NOTE: what you just see is the link:https://www.martinfowler.com/eaaCatalog/activeRecord.html[active record pattern], sometimes just called the entity pattern. +MongoDB with Panache also provides the more classical link:https://martinfowler.com/eaaCatalog/repository.html[repository pattern] via `PanacheMongoRepository`. + == Solution We recommend that you follow the instructions in the next sections and create the application step by step. @@ -94,8 +97,8 @@ If you don't want to generate a new project you can add the dependency in your ` To get started: * add your settings in `{config-file}` -* Make your entities extend `PanacheMongoEntity`, you can use the `@MongoEntity` annotation to specify the name of the database and the name of the collection (it will default to the name of your entity). -* place your entity logic in static methods in your entities +* Make your entities extend `PanacheMongoEntity` (optional if you are using the repository pattern) +* Use the `@MongoEntity` annotation to specify the name of the database and the name of the collection (it will default to the name of your entity). Then add the relevant configuration properties in `{config-file}`. @@ -111,7 +114,9 @@ The `quarkus.mongodb.database` property will be used by MongoDB with Panache to For advanced configuration of the MongoDB client, you can follow the link:mongodb#configuring-the-mongodb-database[Configuring the MongoDB database guide]. -== Defining your entity +== Solution 1: using the active record pattern + +=== Defining your entity To define a Panache entity, simply extend `PanacheMongoEntity` and add your columns as public fields. You can add the `@MongoEntity` annotation to your entity if you need to customize the name of the collection and/or the database. @@ -164,7 +169,7 @@ public class Person extends PanacheMongoEntity { And thanks to our field access rewrite, when your users read `person.name` they will actually call your `getName()` accessor, 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: @@ -226,7 +231,149 @@ List namesButEmmanuels = persons NOTE: A `persistOrUpdate()` method exist that persist or update an entity in the database, it uses the __upsert__ capability of MongoDB to do it in a single query. -== Paging +== 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] +---- +public class Person extends PanacheMongoEntity { + public String name; + public LocalDate birth; + public Status status; + + public static Person findByName(String name){ + return find("name", name).firstResult(); + } + + public static List findAlive(){ + return list("status", Status.Alive); + } + + public static void deleteLoics(){ + delete("name", "Loïc"); + } +} +---- + +== Solution 2: using the repository pattern + +=== Defining your repository + +When using Repositories, you can get the exact same convenient methods as wit the active record pattern injected in your Repository, +by making them implements `PanacheMongoRepository`: + +[source,java] +---- +@ApplicationScoped +public class PersonRepository implements PanacheMongoRepository { + + // put your custom logic here as instance methods + + public Person findByName(String name){ + return find("name", name).firstResult(); + } + + public List findAlive(){ + return list("status", Status.Alive); + } + + public void deleteLoics(){ + delete("name", "Loïc"); + } +} +---- + +All the operations that are defined on `PanacheMongoEntityBase` 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(); +} +---- + +Even with repositories, you can keep your entities as subclasses of `PanacheMongoEntity` in order to get the ID working. +If you don't, you need to specify the ID yourself. +Use what works for you. + +=== 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 = "Loïc"; +person.birth = LocalDate.of(1910, Month.FEBRUARY, 1); +person.status = Status.Alive; + +// persist it +personRepository.persist(person); + +person.status = Status.Dead; + +// Your must call update() in order to send your entity modifications to MongoDB +personRepository.update(person); + +// delete it +personRepository.delete(person); + +// getting a list of all Person entities +List allPersons = personRepository.listAll(); + +// finding a specific person by ID +person = personRepository.findById(personId); + +// finding a specific person by ID via an Optional +Optional optional = personRepository.findByIdOptional(personId); +person = optional.orElseThrow(() -> new NotFoundException()); + +// finding all living persons +List 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(); +---- + +All `list` methods have equivalent `stream` versions. + +[source,java] +---- +Stream persons = personRepository.streamAll(); +List namesButEmmanuels = persons + .map(p -> p.name.toLowerCase() ) + .filter( n -> ! "emmanuel".equals(n) ) + .collect(Collectors.toList()); +---- + +NOTE: A `persistOrUpdate()` method exist that persist or update an entity in the database, it uses the __upsert__ capability of MongoDB to do it in a single query. + +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 collection 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: @@ -263,7 +410,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 an optional `Sort` parameter, which allows you to abstract your sorting: @@ -277,34 +424,7 @@ List 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] ----- -public class Person extends PanacheMongoEntity { - public String name; - public LocalDate birth; - public Status status; - - public static Person findByName(String name){ - return find("name", name).firstResult(); - } - - public static List findAlive(){ - return list("status", Status.Alive); - } - - public static void deleteLoics(){ - delete("name", "Loïc"); - } -} ----- - -== Simplified queries +=== Simplified queries Normally, MongoDB queries are of this form: `{'firstname': 'John', 'lastname':'Doe'}`, this is what we call MongoDB native queries. @@ -327,7 +447,7 @@ We also handle some basic date type transformations: all fields of type `Date`, MongoDB with Panache also supports extended MongoDB queries by providing a `Document` query, this is supported by the find/list/stream/count/delete methods. -== Query parameters +=== Query parameters You can pass query parameters, for both native and PanacheQL queries, by index (1-based) as shown below: @@ -388,7 +508,7 @@ public class Person extends PanacheMongoEntity { Both `findByNameWithPanacheQLQuery()` and `findByNameWithNativeQuery()` methods will return the same result but query written in PanacheQL will use the entity field name: `name`, and native query will use the MongoDB field name: `lastname`. -== Query projection +=== Query projection Query projection can be done with the `project(Class)` method on the `PanacheQuery` object that is returned by the `find()` methods. @@ -436,56 +556,6 @@ TIP: Using @BsonProperty is not needed to define custom column mappings, as the TIP: You can have your projection class extends from another class. In this case, the parent class also needs to have use `@ProjectionFor` annotation. -== 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 `PanacheMongoRepository`: - -[source,java] ----- -@ApplicationScoped -public class PersonRepository implements PanacheMongoRepository { - - // put your custom logic here as instance methods - - public Person findByName(String name){ - return find("name", name).firstResult(); - } - - public List findAlive(){ - return list("status", Status.Alive); - } - - public void deleteLoics(){ - delete("name", "Loïc"); - } -} ----- - -Absolutely all the operations that are defined on `PanacheMongoEntityBase` 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 `PanacheMongoEntity` 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 WARNING: MongoDB offers ACID transactions since version 4.0. MongoDB with Panache doesn't provide support for them.