We know what EntityManagerFactory and EntityManager do. But what’s the HashMap we pass to
CustomPersistenceUnitInfo?
Map<String, String> props = new HashMap<>();
EntityManagerFactory emf = new HibernatePersistenceProvider() .createContainerEntityManagerFactory(new CustomPersistenceUnitInfo("persistence-unit", props));
EntityManager em = emf.createEntityManager();These are properties applied to our context. For instance, while debugging you might add
props.put("hibernate.show_sql", "true") to render SQL queries in the console.
Another useful property (for demos) makes Hibernate manipulate the DB structure behind the scenes
based on your model changes: props.put("hibernate.hbm2ddl.auto", "create"). This is a data
definition language property, set to none by default (use none in production). Values:
create— drops all schema in the DB and recreates tables/schema based on what you’ve specifiednone— the default; does nothing, preferred in production, since that responsibility is usually delegated to DB versioning tools like Flyway or Liquibaseupdate— updates the DB schema based on changes to your model classes
Fun fact: not every find() actually queries the DB. When we persist() an entity, it’s
saved in the context, and only saved to the DB when the transaction commits. So when we find()
an entity, it first checks whether it’s already in the context before querying the DB. Sometimes
you won’t see SQL queries for a find() even with show_sql = true.
find() vs. getReference()
find() fetches the entity from the context, or queries the database if it’s not there — this
normally issues a SELECT. getReference() works differently: it gives you a shell of the
entity, without issuing any query, until you actually do something with that shell.
var e1 = em.getReference(Employee.class, 1); // get employee with id of 1 - no select query yet
System.out.println(e1); // this executes the select statemente1 is just a shell/reference until we actually use it (here, printing it) — that’s what triggers
the query.
refresh()
When you update an entity — e.g. e1.setName("Ernest") — the change is only applied in the
context, and gets saved to the DB when the transaction commits
(em.getTransaction().commit()).
The context is a mirror of the database. Sometimes you want to be sure the context correctly
represents what’s in the database — in other words, that your entity hasn’t been changed in the
context. refresh() does this: it sends a query to the database and updates the context with
whatever’s actually there.
Calling em.refresh(e1) fetches the current DB value of e1 and overwrites the in-context
instance with it. If someone changed the data during the transaction, refreshing resets it back
to what it was at the start (from the DB) — refresh() is effectively an undo of context changes.
// Let's assume e1.name() is "Ernest"
var e1 = em.getReference(Employee.class, 1);System.out.println(e1); // makes the query to the db
e1.setName("Daniel"); // we've updated the name from Ernest to DanielSystem.out.println("Before " + e1); // prints "Daniel"
em.refresh(e1); // resets e1.name() back to ErnestSystem.out.println("After " + e1); // prints "Ernest"ID generation
When you create an entity (a class annotated with @Entity), it’s compulsory to give it a
primary key field, annotated with @Id.
@Entitypublic class Employee {
@Id private int id; // this is our primary key
private String name;
private String address;}With just @Id, generating and managing primary keys is your responsibility — you’d have to set
the id field yourself when creating an Employee. You can delegate this to JPA with
@GeneratedValue(), which lets you choose a generation strategy.
By default the strategy is AUTO, meaning JPA uses the default/automatic implementation of the
particular database’s dialect — not recommended; you should specify it explicitly. Other
GenerationTypes (not all compatible with every database):
IDENTITY— lets the DBMS generate IDs incrementally (auto_incrementin MySQL)SEQUENCE— a good strategy for databases that support sequences (database-level entities that generate values by a specific logic, e.g. an incrementing sequence). Supported by Oracle and PostgreSQL.TABLE— uses a dedicated database table to simulate sequence-like behavior on databases that don’t support sequences. Hibernate creates ahibernate_sequencestable by default. Less efficient than sequences and not commonly recommended for performance (avoid it). Works with any relational database.UUID— lets the DBMS generate universally unique identifiers. Useful, but since UUIDs are binary or string values, indexing them isn’t great for performance in most DBMS — choose the DBMS-specific UUID type where available rather than a plain string. UUIDs offer a much larger ID space than sequential numbers, and are useful when the backend integrates with a frontend (e.g. a URL like/employee/1reveals that/employee/2probably also exists — UUIDs aren’t easily guessable, improving security).
Recommendation: to get the best of both worlds, use an internal numeric ID (INT/Long) as
the primary key — numbers are efficient for indexing and querying, and most databases are
optimized for integer primary keys. This internal ID is used only within the application for
database operations and relationships, so it’s not exposed and presents no security risk.
Then use a UUID as the external identifier exposed to users, APIs, and external systems — it’s harder to guess or predict than a sequential ID, protecting against enumeration attacks. The external ID doesn’t need to be the primary key, but should be indexed for lookups. Since UUIDs are less efficient to index, reserve them for external operations rather than frequent internal lookups — this way your application gets secure public-facing APIs while keeping internal performance high.