From 80ef4777f8b4b155a5405d2f0b2a5c3ffa15752b Mon Sep 17 00:00:00 2001 From: Gavin King Date: Tue, 22 Oct 2024 22:59:44 +0200 Subject: [PATCH] first attempt at intro rewrite for 7 Signed-off-by: Gavin King --- .../introduction/Hibernate_Introduction.adoc | 2 +- .../asciidoc/introduction/Introduction.adoc | 527 ++++++++++-------- .../main/asciidoc/introduction/Preface.adoc | 26 +- .../main/asciidoc/shared/url-attributes.adoc | 1 + 4 files changed, 294 insertions(+), 262 deletions(-) diff --git a/documentation/src/main/asciidoc/introduction/Hibernate_Introduction.adoc b/documentation/src/main/asciidoc/introduction/Hibernate_Introduction.adoc index cf93d8d8b9a5..0f6da553b8f5 100644 --- a/documentation/src/main/asciidoc/introduction/Hibernate_Introduction.adoc +++ b/documentation/src/main/asciidoc/introduction/Hibernate_Introduction.adoc @@ -6,7 +6,7 @@ include::{shared-attributes-dir}/filesystem-attributes.adoc[] include::{shared-attributes-dir}/renderer-attributes.adoc[] -= An Introduction to Hibernate 6 += An Introduction to Hibernate 7 :title-logo-image: image:../../style/asciidoctor/images/org/hibernate/logo.png[] :toc: :toclevels: 3 diff --git a/documentation/src/main/asciidoc/introduction/Introduction.adoc b/documentation/src/main/asciidoc/introduction/Introduction.adoc index 16a196fc9201..e77e7a0b6c9f 100644 --- a/documentation/src/main/asciidoc/introduction/Introduction.adoc +++ b/documentation/src/main/asciidoc/introduction/Introduction.adoc @@ -167,7 +167,7 @@ dependencies { // logging via Log4j implementation 'org.apache.logging.log4j:log4j-core:2.20.0' - // JPA Metamodel Generator + // Hibernate Processor annotationProcessor 'org.hibernate.orm:hibernate-processor:{fullVersion}' // Compile-time checking for HQL @@ -239,30 +239,31 @@ It's the job of this Introduction to make all this crystal clear. ---- package org.hibernate.example; -import org.hibernate.cfg.Configuration; +import org.hibernate.jpa.HibernatePersistenceConfiguration; -import static java.lang.Boolean.TRUE; import static java.lang.System.out; -import static org.hibernate.cfg.AvailableSettings.*; +import static jakarta.persistence.PersistenceConfiguration.*; +import static org.hibernate.cfg.JdbcSettings.*; public class Main { public static void main(String[] args) { - var sessionFactory = new Configuration() - .addAnnotatedClass(Book.class) - // use H2 in-memory database - .setProperty(URL, "jdbc:h2:mem:db1") - .setProperty(USER, "sa") - .setProperty(PASS, "") - // use Agroal connection pool - .setProperty("hibernate.agroal.maxSize", 20) - // display SQL in console - .setProperty(SHOW_SQL, true) - .setProperty(FORMAT_SQL, true) - .setProperty(HIGHLIGHT_SQL, true) - .buildSessionFactory(); + var sessionFactory = + new HibernatePersistenceConfiguration("Bookshelf") + .managedClass(Book.class) + // use H2 in-memory database + .property(JDBC_URL, "jdbc:h2:mem:db1") + .property(JDBC_USER, "sa") + .property(JDBC_PASSWORD, "") + // use Agroal connection pool + .property("hibernate.agroal.maxSize", 20) + // display SQL in console + .property(SHOW_SQL, true) + .property(FORMAT_SQL, true) + .property(HIGHLIGHT_SQL, true) + .createEntityManagerFactory(); // export the inferred database schema - sessionFactory.getSchemaManager().exportMappedObjects(true); + sessionFactory.getSchemaManager().export(true); // persist an entity sessionFactory.inTransaction(session -> { @@ -287,124 +288,122 @@ public class Main { } ---- -Here we've used Hibernate's native APIs. -We could have used JPA-standard APIs to achieve the same thing. - -[[hello-jpa]] -=== Hello, JPA - -If we limit ourselves to the use of JPA-standard APIs, we need to use XML to configure Hibernate. - -[source,xml] -.`META-INF/persistence.xml` ----- - - - - - org.hibernate.example.Book - - - - - - - - - - - - - - - - - - - - - - ----- - -Note that our `build.gradle` and `log4j2.properties` files are unchanged. - -Our entity class is also unchanged from what we had before. - -Unfortunately, JPA doesn't offer an `inSession()` method, so we'll have to implement session and transaction management ourselves. -We can put that logic in our own `inSession()` function, so that we don't have to repeat it for every transaction. -Again, you don't need to understand any of this code right now. - -[[main-jpa]] -[source,java] -.`Main.java` (JPA version) ----- -package org.hibernate.example; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.EntityManagerFactory; - -import java.util.Map; -import java.util.function.Consumer; - -import static jakarta.persistence.Persistence.createEntityManagerFactory; -import static java.lang.System.out; -import static org.hibernate.cfg.AvailableSettings.JAKARTA_HBM2DDL_DATABASE_ACTION; -import static org.hibernate.tool.schema.Action.CREATE; - -public class Main { - public static void main(String[] args) { - var factory = createEntityManagerFactory("example", - // export the inferred database schema - Map.of(JAKARTA_HBM2DDL_DATABASE_ACTION, CREATE)); - - // persist an entity - inSession(factory, entityManager -> { - entityManager.persist(new Book("9781932394153", "Hibernate in Action")); - }); - - // query data using HQL - inSession(factory, entityManager -> { - out.println(entityManager.createQuery("select isbn||': '||title from Book").getSingleResult()); - }); - - // query data using criteria API - inSession(factory, entityManager -> { - var builder = factory.getCriteriaBuilder(); - var query = builder.createQuery(String.class); - var book = query.from(Book.class); - query.select(builder.concat(builder.concat(book.get(Book_.isbn), builder.literal(": ")), - book.get(Book_.title))); - out.println(entityManager.createQuery(query).getSingleResult()); - }); - } - - // do some work in a session, performing correct transaction management - static void inSession(EntityManagerFactory factory, Consumer work) { - var entityManager = factory.createEntityManager(); - var transaction = entityManager.getTransaction(); - try { - transaction.begin(); - work.accept(entityManager); - transaction.commit(); - } - catch (Exception e) { - if (transaction.isActive()) transaction.rollback(); - throw e; - } - finally { - entityManager.close(); - } - } -} ----- +// +// [[hello-jpa]] +// === Hello, JPA +// +// If we limit ourselves to the use of JPA-standard APIs, we need to use XML to configure Hibernate. +// +// [source,xml] +// .`META-INF/persistence.xml` +// ---- +// +// +// +// +// org.hibernate.example.Book +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// ---- +// +// Note that our `build.gradle` and `log4j2.properties` files are unchanged. +// +// Our entity class is also unchanged from what we had before. +// +// Unfortunately, JPA doesn't offer an `inSession()` method, so we'll have to implement session and transaction management ourselves. +// We can put that logic in our own `inSession()` function, so that we don't have to repeat it for every transaction. +// Again, you don't need to understand any of this code right now. +// +// [[main-jpa]] +// [source,java] +// .`Main.java` (JPA version) +// ---- +// package org.hibernate.example; +// +// import jakarta.persistence.EntityManager; +// import jakarta.persistence.EntityManagerFactory; +// +// import java.util.Map; +// import java.util.function.Consumer; +// +// import static jakarta.persistence.Persistence.createEntityManagerFactory; +// import static java.lang.System.out; +// import static org.hibernate.cfg.AvailableSettings.JAKARTA_HBM2DDL_DATABASE_ACTION; +// import static org.hibernate.tool.schema.Action.CREATE; +// +// public class Main { +// public static void main(String[] args) { +// var factory = createEntityManagerFactory("example", +// // export the inferred database schema +// Map.of(JAKARTA_HBM2DDL_DATABASE_ACTION, CREATE)); +// +// // persist an entity +// inSession(factory, entityManager -> { +// entityManager.persist(new Book("9781932394153", "Hibernate in Action")); +// }); +// +// // query data using HQL +// inSession(factory, entityManager -> { +// out.println(entityManager.createQuery("select isbn||': '||title from Book").getSingleResult()); +// }); +// +// // query data using criteria API +// inSession(factory, entityManager -> { +// var builder = factory.getCriteriaBuilder(); +// var query = builder.createQuery(String.class); +// var book = query.from(Book.class); +// query.select(builder.concat(builder.concat(book.get(Book_.isbn), builder.literal(": ")), +// book.get(Book_.title))); +// out.println(entityManager.createQuery(query).getSingleResult()); +// }); +// } +// +// // do some work in a session, performing correct transaction management +// static void inSession(EntityManagerFactory factory, Consumer work) { +// var entityManager = factory.createEntityManager(); +// var transaction = entityManager.getTransaction(); +// try { +// transaction.begin(); +// work.accept(entityManager); +// transaction.commit(); +// } +// catch (Exception e) { +// if (transaction.isActive()) transaction.rollback(); +// throw e; +// } +// finally { +// entityManager.close(); +// } +// } +// } +// ---- In practice, we never access the database directly from a `main()` method. So now let's talk about how to organize persistence logic in a real system. @@ -425,7 +424,7 @@ We're going to ask you to suppress this urge for now. [TIP] ==== The _easiest_ way to use Hibernate is to call the `Session` or `EntityManager` directly. -If you're new to Hibernate, frameworks which wrap JPA are only going to make your life more difficult. +If you're new to Hibernate, frameworks which wrap JPA are quite likely to make your life more difficult. ==== We prefer a _bottom-up_ approach to organizing our code. @@ -438,11 +437,13 @@ We might start with something like this, a mix of UI and persistence logic: ---- @Path("/") @Produces("application/json") public class BookResource { + @GET @Path("book/{isbn}") public Book getBook(String isbn) { var book = sessionFactory.fromTransaction(session -> session.find(Book.class, isbn)); return book == null ? Response.status(404).build() : book; } + } ---- Indeed, we might also _finish_ with something like that—it's quite hard to identify anything concretely wrong with the code above, and for such a simple case it seems really difficult to justify making this code more complicated by introducing additional objects. @@ -498,7 +499,7 @@ This is an example of a _query method_, a function which accepts arguments to th And that's all it does; it doesn't orchestrate additional program logic, and it doesn't perform transaction or session management. It's even better to specify the query string using the `@NamedQuery` annotation, so that Hibernate can validate the query at startup time, that is, when the `SessionFactory` is created, instead of when the query is first executed. -Indeed, since we included the <> in our <>, the query can even be validated at _compile time_. +Indeed, since we included <> in our <>, the query can even be validated at _compile time_. We need a place to put the annotation, so let's move our query method to a new class: @@ -511,7 +512,7 @@ class Queries { static List findBooksByTitleWithPagination(Session session, String titlePattern, Page page) { - return session.createNamedQuery("findBooksByTitle", Book.class) + return session.createNamedQuery(Queries_._findBooksByTitle_) //type safe reference to the named query .setParameter("title", titlePattern) .setPage(page) .getResultList(); @@ -521,7 +522,7 @@ class Queries { Notice that our query method doesn't attempt to hide the `EntityManager` from its clients. Indeed, the client code is responsible for providing the `EntityManager` or `Session` to the query method. -This is a quite distinctive feature of our whole approach. +// This is a quite distinctive feature of our whole approach. The client code may: @@ -537,16 +538,17 @@ Whatever the case, the code which orchestrates a unit of work usually just calls public List findBooks(String titlePattern) { var books = sessionFactory.fromTransaction(session -> Queries.findBooksByTitleWithPagination(session, titlePattern, - Page.page(RESULTS_PER_PAGE, page)); + Page.page(RESULTS_PER_PAGE, page))); return books.isEmpty() ? Response.status(404).build() : books; } ---- You might be thinking that our query method looks a bit boilerplatey. -That's true, perhaps, but we're much more concerned that it's not very typesafe. +That's true, perhaps, but we're much more concerned that it's still not perfectly typesafe. Indeed, for many years, the lack of compile-time checking for HQL queries and code which binds arguments to query parameters was our number one source of discomfort with Hibernate. +Here, the `@CheckHQL` annotation takes care of checking the query itself, but the call to `setParameter()` is still not type safe. -Fortunately, there's now a solution to both problems: as an incubating feature of Hibernate 6.3, we now offer the possibility to have the Metamodel Generator fill in the implementation of such query methods for you. +Fortunately, there's now a great solution to both problems. Hibernate Processor is able to fill in the implementation of such query methods for us. This facility is the topic of <>, so for now we'll just leave you with one simple example. Suppose we simplify `Queries` to just the following: @@ -559,7 +561,7 @@ interface Queries { } ---- -Then the Metamodel Generator automatically produces an implementation of the method annotated `@HQL` in a class named `Queries_`. +Then Hibernate Processor automatically produces an implementation of the method annotated `@HQL` in a class named `Queries_`. We can call it just like we called our handwritten version: [source,java] @@ -569,7 +571,7 @@ We can call it just like we called our handwritten version: public List findBooks(String titlePattern) { var books = sessionFactory.fromTransaction(session -> Queries_.findBooksByTitleWithPagination(session, titlePattern, - Page.page(RESULTS_PER_PAGE, page)); + Page.page(RESULTS_PER_PAGE, page))); return books.isEmpty() ? Response.status(404).build() : books; } ---- @@ -578,15 +580,41 @@ In this case, the quantity of code eliminated is pretty trivial. The real value is in improved type safety. We now find out about errors in assignments of arguments to query parameters at compile time. -[NOTE] +This is all quite nice so far, but at this point you're probably wondering whether we could use dependency injection to obtain an _instance_ of the `Queries` interface. +Well, indeed we can. +What we need to do is indicate the kind of session the `Queries` interface depends on, by adding a method to retrieve the session. + +[source,java] +---- +interface Queries { + EntityManager entityManager(); + + @HQL("where title like :title order by title") + List findBooksByTitleWithPagination(String title, Page page); +} +---- + +The `Queries` interface is now considered a _repository_, and we may use CDI to inject the repository implementation generated by Hibernate Processor: + +[source,java] +---- +@Inject Queries queries; + +@GET +@Path("books/{titlePattern}") +@Transactional +public List findBooks(String titlePattern) { + var books = queries.findBooksByTitleWithPagination(session, titlePattern, + Page.page(RESULTS_PER_PAGE, page)); + return books.isEmpty() ? Response.status(404).build() : books; +} +---- + +Alternatively, if CDI isn't available, we may directly instantiate the generated repository implementation class using `new Queries_(entityManager)`. + +[TIP] ==== -At this point, we're certain you're full of doubts about this idea. -And quite rightly so. -We would love to answer your objections right here, but that will take us much too far off track. -So we ask you to file away these thoughts for now. -We promise to make it make sense when we <>. -And, after that, if you still don't like this approach, please understand that it's completely optional. -Nobody's going to come around to your house to force it down your throat. +The Jakarta Data specification now formalizes this approach, and Hibernate Processor provides an implementation, which we've branded _Hibernate Data Repositories_. ==== Now that we have a rough picture of what our persistence logic might look like, it's natural to ask how we should test our code. @@ -617,11 +645,11 @@ Whether we're testing against our real database, or against an in-memory Java da We _usually_ do this when we create the Hibernate `SessionFactory` or JPA `EntityManagerFactory`, and so traditionally we've used a <> for this. The JPA-standard property is `jakarta.persistence.schema-generation.database.action`. -For example, if we're using `Configuration` to configure Hibernate, we could write: +For example, if we're using `PersistenceConfiguration` to configure Hibernate, we could write: [source,java] ---- -configuration.setProperty(AvailableSettings.JAKARTA_HBM2DDL_DATABASE_ACTION, +configuration.property(PersistenceConfiguration.SCHEMAGEN_DATABASE_ACTION, Action.SPEC_ACTION_DROP_AND_CREATE); ---- @@ -660,11 +688,11 @@ insert into Books (isbn, title) values ('9781617290459', 'Java Persistence with If we name this file `import.sql`, and place it in the root classpath, that's all we need to do. Otherwise, we need to specify the file in the <> `jakarta.persistence.sql-load-script-source`. -If we're using `Configuration` to configure Hibernate, we could write: +If we're using `PersistenceConfiguration` to configure Hibernate, we could write: [source,java] ---- -configuration.setProperty(AvailableSettings.JAKARTA_HBM2DDL_LOAD_SCRIPT_SOURCE, +configuration.property(AvailableSettings.JAKARTA_HBM2DDL_LOAD_SCRIPT_SOURCE, "/org/example/test-data.sql"); ---- @@ -698,14 +726,14 @@ Another important test we'll need is one which validates our <>. +If you're only interested in objective facts, +// , or if you prefer not to read things that might undermine the opinion you currently hold, +please feel free to skip straight to the <>. ==== Hibernate is an architecture-agnostic library, not a framework, and therefore integrates comfortably with a wide range of Java frameworks and containers. @@ -726,111 +756,118 @@ Consistent with our place within the ecosystem, we've historically avoided givin This is a practice we're now perhaps inclined to regret, since the resulting vacuum has come to be filled with advice from people advocating architectures, design patterns, and extra frameworks which we suspect make Hibernate a bit less pleasant to use than it should be. In particular, frameworks which wrap JPA seem to add bloat while subtracting some of the fine-grained control over data access that Hibernate works so hard to provide. -These frameworks don't expose the full feature set of Hibernate, and so the program is forced to work with a less powerful abstraction. +These frameworks don't expose the full feature set of Hibernate--nor do they provide much interesting functionality of their own--and so the program is forced to work with a less powerful abstraction. The stodgy, dogmatic, _conventional_ wisdom, which we hesitate to challenge for simple fear of pricking ourselves on the erect hackles that inevitably accompany such dogma-baiting is: > Code which interacts with the database belongs in a separate _persistence layer_. We lack the courage—perhaps even the conviction—to tell you categorically to _not_ follow this recommendation. -But we do ask you to consider the cost in boilerplate of any architectural layer, and whether the benefits this cost buys are really worth it in the context of your system. - -To add a little background texture to this discussion, and at the risk of our Introduction degenerating into a rant at such an early stage, we're going ask you to humor us while talk a little more about ancient history. - -[%unbreakable] -.An epic tale of DAOs and Repositories -**** -Back in the dark days of Java EE 4, before the standardization of Hibernate, and subsequent ascendance of JPA in Java enterprise development, it was common to hand-code the messy JDBC interactions that Hibernate takes care of today. -In those terrible times, a pattern arose that we used to call _Data Access Objects_ (DAOs). -A DAO gave you a place to put all that nasty JDBC code, leaving the important program logic cleaner. - -When Hibernate arrived suddenly on the scene in 2001, developers loved it. -But Hibernate implemented no specification, and many wished to reduce or at least _localize_ the dependence of their project logic on Hibernate. -An obvious solution was to keep the DAOs around, but to replace the JDBC code inside them with calls to the Hibernate `Session`. - -We partly blame ourselves for what happened next. - -Back in 2002 and 2003 this really seemed like a pretty reasonable thing to do. -In fact, we contributed to the popularity of this approach by recommending—or at least not discouraging—the use of DAOs in _Hibernate in Action_. -We hereby apologize for this mistake, and for taking much too long to recognize it. - -Eventually, some folks came to believe that their DAOs shielded their program from depending in a hard way on ORM, allowing them to "swap out" Hibernate, and replace it with JDBC, or with something else. -In fact, this was never really true—there's quite a deep difference between the programming model of JDBC, where every interaction with the database is explicit and synchronous, and the programming model of stateful sessions in Hibernate, where updates are implicit, and SQL statements are executed asynchronously. +But we do ask you to consider the cost in boilerplate of any architectural layer, and whether the benefits this cost buys are really worth it, in the context of your system. -But then the whole landscape for persistence in Java changed in April 2006, when the final draft of JPA 1.0 was approved. -Java now had a standard way to do ORM, with multiple high-quality implementations of the standard API. -This was the end of the line for the DAOs, right? - -Well, no. -It wasn't. -DAOs were rebranded "repositories", and continue to enjoy a sort-of zombie afterlife as a front-end to JPA. -But are they really pulling their weight, or are they just unnecessary extra complexity and bloat? An extra layer of indirection that makes stack traces harder to read and code harder to debug? - -Our considered view is that they're mostly just bloat. -The JPA `EntityManager` is a "repository", and it's a standard repository with a well-defined specification written by people who spend all day thinking about persistence. -If these repository frameworks offered anything actually _useful_—and not obviously foot-shooty—over and above what `EntityManager` provides, we would have already added it to `EntityManager` decades ago. -**** +// To add a little background texture to this discussion, and at the risk of our Introduction degenerating into a rant at such an early stage, we're going ask you to humor us while talk a little more about ancient history. +// +// [%unbreakable] +// .An epic tale of DAOs and Repositories +// **** +// Back in the dark days of Java EE 4, before the standardization of Hibernate, and subsequent ascendance of JPA in Java enterprise development, it was common to hand-code the messy JDBC interactions that Hibernate takes care of today. +// In those terrible times, a pattern arose that we used to call _Data Access Objects_ (DAOs). +// A DAO gave you a place to put all that nasty JDBC code, leaving the important program logic cleaner. +// +// When Hibernate arrived suddenly on the scene in 2001, developers loved it. +// But Hibernate implemented no specification, and many wished to reduce or at least _localize_ the dependence of their project logic on Hibernate. +// An obvious solution was to keep the DAOs around, but to replace the JDBC code inside them with calls to the Hibernate `Session`. +// +// We partly blame ourselves for what happened next. +// +// Back in 2002 and 2003 this really seemed like a pretty reasonable thing to do. +// In fact, we contributed to the popularity of this approach by recommending—or at least not discouraging—the use of DAOs in _Hibernate in Action_. +// We hereby apologize for this mistake, and for taking much too long to recognize it. +// +// Eventually, some folks came to believe that their DAOs shielded their program from depending in a hard way on ORM, allowing them to "swap out" Hibernate, and replace it with JDBC, or with something else. +// In fact, this was never really true—there's quite a deep difference between the programming model of JDBC, where every interaction with the database is explicit and synchronous, and the programming model of stateful sessions in Hibernate, where updates are implicit, and SQL statements are executed asynchronously. +// +// But then the whole landscape for persistence in Java changed in April 2006, when the final draft of JPA 1.0 was approved. +// Java now had a standard way to do ORM, with multiple high-quality implementations of the standard API. +// This was the end of the line for the DAOs, right? +// +// Well, no. +// It wasn't. +// DAOs were rebranded "repositories", and continue to enjoy a sort-of zombie afterlife as a front-end to JPA. +// But are they really pulling their weight, or are they just unnecessary extra complexity and bloat? An extra layer of indirection that makes stack traces harder to read and code harder to debug? +// +// Our considered view is that they're mostly just bloat. +// The JPA `EntityManager` is a "repository", and it's a standard repository with a well-defined specification written by people who spend all day thinking about persistence. +// If these repository frameworks offered anything actually _useful_—and not obviously foot-shooty—over and above what `EntityManager` provides, we would have already added it to `EntityManager` decades ago. +// **** -Ultimately, we're not sure you need a separate persistence layer at all. -At least _consider_ the possibility that it might be OK to call the `EntityManager` directly from your business logic. +// Ultimately, we're not sure you need a separate persistence layer at all. +That is to say, we beg you to please at least _consider_ the possibility that it might be OK to call the `EntityManager` directly from your business logic. image::images/architecture.png[API overview,pdfwidth="100%",width=1100,align="center"] -We can already hear you hissing at our heresy. +We already hear you hissing at our heresy. But before slamming shut the lid of your laptop and heading off to fetch garlic and a pitchfork, take a couple of hours to weigh what we're proposing. -OK, so, look, if it makes you feel better, one way to view `EntityManager` is to think of it as a single _generic_ "repository" that works for every entity in your system. -From this point of view, JPA _is_ your persistence layer. -And there's few good reasons to wrap this abstraction in a second abstraction that's _less_ generic. - -// We might even analogize `EntityManager` to `List`. -// Then DAO-style repositories would be like having separate `StringList`, `IntList`, `PersonList`, and `BookList` classes. -// They're a parallel class hierarchy that makes the data model harder to evolve over time. - -// Of course, such decisions are highly context-dependent: surely _some_ programs out there really do benefit from isolating the persistence logic into some sort of distinct layer; on the other hand, we're equally sure that there are others which simply _don't_. - -Even where a distinct persistence layer _is_ appropriate, DAO-style repositories aren't the unambiguously most-correct way to factorize the equation: - -- most nontrivial queries touch multiple entities, and so it's often quite ambiguous which repository such a query belongs to, -- most queries are extremely specific to a particular fragment of program logic, and aren't reused in different places across the system, and -- the various operations of a repository rarely interact or share common internal implementation details. - -Indeed, repositories, by nature, exhibit very low _cohesion_. -A layer of repository objects might make sense if you have multiple implementations of each repository, but in practice almost nobody ever does. -That's because they're also extremely highly _coupled_ to their clients, with a very large API surface. -And, on the contrary, a layer is only easily replaceable if it has a very _narrow_ API. +Perhaps this is an argument we're bound to lose. +If that's the case, we recommend the Jakarta Data specification as a foundation for your persistence layer. +Our implementation of this specification, Hibernate Data Repositories, is built in to Hibernate Processor, and so you probably already have it available in your program. -[%unbreakable] -[TIP] -==== -Some people do indeed use mock repositories for testing, but we really struggle to see any value in this. -If we don't want to run our tests against our real database, it's usually very easy to "mock" the database itself by running tests against an in-memory Java database like H2. -This works even better in Hibernate 6 than in older versions of Hibernate, since HQL is now _much_ more portable between platforms. -==== - -// So even in cases where separation _is_ of benefit, we go on to question the notion that this must be achieved via a layer of container-managed objects. +Unlike other repository frameworks, Hibernate Data Repositories offers something that plain JPA simply doesn't have: full compile-time type safety for your queries. +To learn more, please refer to _Introducing Hibernate Data Repositories_. -// That said, one thing we _do_ understand is the desire to package: +// OK, so, look, if it makes you feel better, one way to view `EntityManager` is to think of it as a single _generic_ "repository" that works for every entity in your system. +// From this point of view, JPA _is_ your persistence layer. +// And there's few good reasons to wrap this abstraction in a second abstraction that's _less_ generic. // -// - a HQL or SQL query string with -// - the code which binds its parameters +// // We might even analogize `EntityManager` to `List`. +// // Then DAO-style repositories would be like having separate `StringList`, `IntList`, `PersonList`, and `BookList` classes. +// // They're a parallel class hierarchy that makes the data model harder to evolve over time. // -// as a typesafe function. -// DAO-style repositories seem to provide a very natural place to hang such functions, and we suspect that this accounts for at least some of their continued popularity. -// You're probably wondering how _we_ would go about defining such functions. - -// One thing that some repository frameworks offer is the ability to declare an abstract method that queries the database, and have the framework fill in an implementation of the method. -// But the way this works is that you must encode your query into the name of the method itself. +// // Of course, such decisions are highly context-dependent: surely _some_ programs out there really do benefit from isolating the persistence logic into some sort of distinct layer; on the other hand, we're equally sure that there are others which simply _don't_. +// +// Even where a distinct persistence layer _is_ appropriate, DAO-style repositories aren't the unambiguously most-correct way to factorize the equation: +// +// - most nontrivial queries touch multiple entities, and so it's often quite ambiguous which repository such a query belongs to, +// - most queries are extremely specific to a particular fragment of program logic, and aren't reused in different places across the system, and +// - the various operations of a repository rarely interact or share common internal implementation details. +// +// Indeed, repositories, by nature, exhibit very low _cohesion_. +// A layer of repository objects might make sense if you have multiple implementations of each repository, but in practice almost nobody ever does. +// That's because they're also extremely highly _coupled_ to their clients, with a very large API surface. +// And, on the contrary, a layer is only easily replaceable if it has a very _narrow_ API. // -// Which, at least in principle, for a not-very-complicated query, leads to a method name like this: +// [%unbreakable] +// [TIP] +// ==== +// Some people do indeed use mock repositories for testing, but we really struggle to see any value in this. +// If we don't want to run our tests against our real database, it's usually very easy to "mock" the database itself by running tests against an in-memory Java database like H2. +// This works even better in Hibernate 6 than in older versions of Hibernate, since HQL is now _much_ more portable between platforms. +// ==== // -// [.text-center] -// `findFirst10ByOrderDistinctPeopleByLastnameOrFirstnameAsc` +// // So even in cases where separation _is_ of benefit, we go on to question the notion that this must be achieved via a layer of container-managed objects. // -// This is a much worse query language than HQL. -// I think you can see why we didn't implement this idea in Hibernate. +// // That said, one thing we _do_ understand is the desire to package: +// // +// // - a HQL or SQL query string with +// // - the code which binds its parameters +// // +// // as a typesafe function. +// // DAO-style repositories seem to provide a very natural place to hang such functions, and we suspect that this accounts for at least some of their continued popularity. +// // You're probably wondering how _we_ would go about defining such functions. // -_Phew_, let's move on. +// // One thing that some repository frameworks offer is the ability to declare an abstract method that queries the database, and have the framework fill in an implementation of the method. +// // But the way this works is that you must encode your query into the name of the method itself. +// // +// // Which, at least in principle, for a not-very-complicated query, leads to a method name like this: +// // +// // [.text-center] +// // `findFirst10ByOrderDistinctPeopleByLastnameOrFirstnameAsc` +// // +// // This is a much worse query language than HQL. +// // I think you can see why we didn't implement this idea in Hibernate. +// // +// _Phew_, let's move on. [[overview]] === Overview @@ -843,7 +880,7 @@ This introduction will guide you through the basic tasks involved in developing 2. writing a _domain model_, that is, a set of _entity classes_ which represent the persistent types in your program, and which map to tables of your database, 3. customizing these mappings when the model maps to a pre-existing relational schema, 4. using the `Session` or `EntityManager` to perform operations which query the database and return entity instances, or which update the data held in the database, -5. using the Hibernate Metamodel Generator to improve compile-time type-safety, +5. using Hibernate Processor to improve compile-time type-safety, 6. writing complex queries using the Hibernate Query Language (HQL) or native SQL, and, finally 7. tuning performance of the data access logic. diff --git a/documentation/src/main/asciidoc/introduction/Preface.adoc b/documentation/src/main/asciidoc/introduction/Preface.adoc index 0c1020a7e358..2e39096abd50 100644 --- a/documentation/src/main/asciidoc/introduction/Preface.adoc +++ b/documentation/src/main/asciidoc/introduction/Preface.adoc @@ -1,31 +1,25 @@ [[preface]] == Preface -Hibernate 6 is a major redesign of the world's most popular and feature-rich ORM solution. -The redesign has touched almost every subsystem of Hibernate, including the APIs, mapping annotations, and the query language. -This new Hibernate is more powerful, more robust, and more typesafe. +Hibernate 6 was a major redesign of the world's most popular and feature-rich ORM solution. +The redesign touched almost every subsystem of Hibernate, including the APIs, mapping annotations, and the query language. +This new Hibernate was suddenly more powerful, more robust, more portable, and more type safe. -With so many improvements, it's very difficult to summarize the significance of this work. -But the following general themes stand out. -Hibernate 6: +Hibernate 7 builds on this foundation, adds support for JPA 3.2, and introduces Hibernate Data Repositories, an implementation of the Jakarta Data specification. +Taken together, these enhancements yield a level of compile-time type safety--and resulting developer productivity--which was previously impossible. +Hibernate Data Repositories offers truly seamless integration of the ORM solution with the persistence layer, obsoleting older add-on repository frameworks. -- finally takes advantage of the advances in relational databases over the past decade, updating the query language to support a raft of new constructs in modern dialects of SQL, -- exhibits much more consistent behavior across different databases, greatly improving portability, and generates much higher-quality DDL from dialect-independent code, -- improves error reporting by more scrupulous validation of queries _before_ access to the database, -- improves the type-safety of O/R mapping annotations, clarifies the separation of API, SPI, and internal implementation, and fixes some long-standing architectural flaws, -- removes or deprecates legacy APIs, laying the foundation for future evolution, and -- makes far better use of Javadoc, putting much more information at the fingertips of developers. +Hibernate and Hibernate Reactive are core components of Quarkus 3, the most exciting new environment for cloud-native development in Java, and Hibernate remains the persistence solution of choice for almost every major Java framework or server. -Hibernate 6 and Hibernate Reactive are now core components of Quarkus 3, the most exciting new environment for cloud-native development in Java, and Hibernate remains the persistence solution of choice for almost every major Java framework or server. - -Unfortunately, the changes in Hibernate 6 have obsoleted much of the information about Hibernate that's available in books, in blog posts, and on stackoverflow. +Unfortunately, the changes in Hibernate 6 obsoleted much of the information about Hibernate that's available in books, in blog posts, and on stackoverflow. This guide is an up-to-date, high-level discussion of the current feature set and recommended usage. It does not attempt to cover every feature and should be used in conjunction with other documentation: - Hibernate's extensive link:{doc-javadoc-url}[Javadoc], -- the link:{doc-query-language-url}[Guide to Hibernate Query Language], and +- the link:{doc-query-language-url}[Guide to Hibernate Query Language], +- link:{doc-data-repositories-url}[Introducing Hibernate Data Repositories], and - the Hibernate link:{doc-user-guide-url}[User Guide]. [NOTE] diff --git a/documentation/src/main/asciidoc/shared/url-attributes.adoc b/documentation/src/main/asciidoc/shared/url-attributes.adoc index 12c29a944d4f..3008ae3ac3e9 100644 --- a/documentation/src/main/asciidoc/shared/url-attributes.adoc +++ b/documentation/src/main/asciidoc/shared/url-attributes.adoc @@ -10,6 +10,7 @@ include::./common-attributes.adoc[] :doc-quick-start-url: {doc-version-base-url}/quickstart/html_single/ :doc-query-language-url: {doc-version-base-url}/querylanguage/html_single/Hibernate_Query_Language.html :doc-introduction-url: {doc-version-base-url}/introduction/html_single/Hibernate_Introduction.html +:doc-data-repositories-url: {doc-version-base-url}/repositories/html_single/Hibernate_Data_Repositories.html :doc-user-guide-url: {doc-version-base-url}/userguide/html_single/Hibernate_User_Guide.html :doc-javadoc-url: {doc-version-base-url}/javadocs/ :doc-topical-url: {doc-version-base-url}/topical/html_single/