OpenJPA
OpenJPA is Apache Software Foundation's JPA (Java Persistence API) implementation. It provides bytecode enhancement and query optimization features, characterized by lightweight and simple design as an open-source ORM. While complying with standard JPA specifications, it achieves flexible data access through proprietary extensions.
Library
OpenJPA
Overview
OpenJPA is Apache Software Foundation's JPA (Java Persistence API) implementation. It provides bytecode enhancement and query optimization features, characterized by lightweight and simple design as an open-source ORM. While complying with standard JPA specifications, it achieves flexible data access through proprietary extensions.
Details
OpenJPA 2025 edition continues as a stable open-source choice. Used as a simple JPA implementation for small to medium projects, it provides reliable solutions through Apache Software Foundation's quality standards. It includes features necessary for enterprise applications such as transparent persistence through bytecode enhancement, comprehensive caching mechanisms, and flexible fetch strategies.
Key Features
- JPA Standard Compliance: Complete implementation of JPA 2.2 specification
- Bytecode Enhancement: Efficient entity management at runtime
- Comprehensive Caching: Full support for L1/L2 cache
- Query Optimization: Automatic SQL query optimization
- Pluggable Architecture: Integration of custom providers
- Apache Quality: Strict quality control and security
Pros and Cons
Pros
- Reliability from Apache Software Foundation
- Fully open source (Apache 2.0 license)
- Lightweight and easy to implement
- Compliance with standard JPA specifications
- High performance through bytecode enhancement
- Active community support
Cons
- Limited features compared to Hibernate
- Lack of enterprise features
- Relatively less documentation
- Slow adoption of latest Java features
- Limited commercial support options
References
Examples
Basic Setup
<!-- pom.xml -->
<dependencies>
<!-- OpenJPA -->
<dependency>
<groupId>org.apache.openjpa</groupId>
<artifactId>openjpa</artifactId>
<version>3.2.2</version>
</dependency>
<!-- Database driver (PostgreSQL example) -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.5.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- OpenJPA Enhancement Plugin -->
<plugin>
<groupId>org.apache.openjpa</groupId>
<artifactId>openjpa-maven-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<includes>**/entity/*.class</includes>
<addDefaultConstructor>true</addDefaultConstructor>
<enforcePropertyRestrictions>true</enforcePropertyRestrictions>
</configuration>
<executions>
<execution>
<id>enhancer</id>
<phase>process-classes</phase>
<goals>
<goal>enhance</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Entity Definition
// User.java
package com.example.entity;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "users")
@NamedQueries({
@NamedQuery(
name = "User.findByEmail",
query = "SELECT u FROM User u WHERE u.email = :email"
),
@NamedQuery(
name = "User.findActiveUsers",
query = "SELECT u FROM User u WHERE u.active = true ORDER BY u.name"
)
})
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
private String name;
@Column(nullable = false, unique = true, length = 255)
private String email;
private Integer age;
@Column(name = "is_active")
private boolean active = true;
@Column(name = "created_at", nullable = false)
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
// Relations
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Post> posts = new ArrayList<>();
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(
name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private List<Role> roles = new ArrayList<>();
// Lifecycle callbacks
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
// Constructors, getters, setters
public User() {}
public User(String name, String email) {
this.name = name;
this.email = email;
}
// ... getters and setters
}
// Post.java
@Entity
@Table(name = "posts")
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 200)
private String title;
@Lob
private String content;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "author_id", nullable = false)
private User author;
@Column(name = "view_count")
private int viewCount = 0;
@Column(name = "created_at")
private LocalDateTime createdAt;
// ... constructors, getters, setters
}
// Role.java
@Entity
@Table(name = "roles")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String name;
@ManyToMany(mappedBy = "roles")
private List<User> users = new ArrayList<>();
// ... constructors, getters, setters
}
Persistence Context Configuration
<!-- META-INF/persistence.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<persistence-unit name="BlogPU" transaction-type="RESOURCE_LOCAL">
<provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
<!-- Entity classes -->
<class>com.example.entity.User</class>
<class>com.example.entity.Post</class>
<class>com.example.entity.Role</class>
<properties>
<!-- Database connection settings -->
<property name="javax.persistence.jdbc.driver"
value="org.postgresql.Driver"/>
<property name="javax.persistence.jdbc.url"
value="jdbc:postgresql://localhost:5432/blogdb"/>
<property name="javax.persistence.jdbc.user" value="dbuser"/>
<property name="javax.persistence.jdbc.password" value="dbpass"/>
<!-- OpenJPA specific settings -->
<property name="openjpa.ConnectionFactoryProperties"
value="PrintParameters=true"/>
<property name="openjpa.RuntimeUnenhancedClasses" value="supported"/>
<property name="openjpa.jdbc.SynchronizeMappings"
value="buildSchema(ForeignKeys=true)"/>
<!-- Caching settings -->
<property name="openjpa.DataCache" value="true"/>
<property name="openjpa.QueryCache" value="true"/>
<property name="openjpa.RemoteCommitProvider" value="sjvm"/>
<!-- Logging -->
<property name="openjpa.Log"
value="DefaultLevel=INFO, SQL=TRACE, Tool=INFO"/>
</properties>
</persistence-unit>
</persistence>
Basic CRUD Operations
// UserRepository.java
package com.example.repository;
import com.example.entity.User;
import javax.persistence.*;
import java.util.List;
import java.util.Optional;
public class UserRepository {
private EntityManagerFactory emf;
public UserRepository() {
this.emf = Persistence.createEntityManagerFactory("BlogPU");
}
// CREATE - Create new record
public User createUser(User user) {
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
em.persist(user);
tx.commit();
return user;
} catch (Exception e) {
if (tx.isActive()) {
tx.rollback();
}
throw new RuntimeException("Failed to create user", e);
} finally {
em.close();
}
}
// READ - Read records
public Optional<User> findById(Long id) {
EntityManager em = emf.createEntityManager();
try {
User user = em.find(User.class, id);
return Optional.ofNullable(user);
} finally {
em.close();
}
}
public List<User> findAll() {
EntityManager em = emf.createEntityManager();
try {
TypedQuery<User> query = em.createQuery(
"SELECT u FROM User u ORDER BY u.name", User.class);
return query.getResultList();
} finally {
em.close();
}
}
// Using named queries
public Optional<User> findByEmail(String email) {
EntityManager em = emf.createEntityManager();
try {
TypedQuery<User> query = em.createNamedQuery(
"User.findByEmail", User.class);
query.setParameter("email", email);
List<User> results = query.getResultList();
return results.isEmpty() ? Optional.empty() : Optional.of(results.get(0));
} finally {
em.close();
}
}
// UPDATE - Update records
public User updateUser(User user) {
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
User mergedUser = em.merge(user);
tx.commit();
return mergedUser;
} catch (Exception e) {
if (tx.isActive()) {
tx.rollback();
}
throw new RuntimeException("Failed to update user", e);
} finally {
em.close();
}
}
// DELETE - Delete records
public void deleteUser(Long id) {
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
User user = em.find(User.class, id);
if (user != null) {
em.remove(user);
}
tx.commit();
} catch (Exception e) {
if (tx.isActive()) {
tx.rollback();
}
throw new RuntimeException("Failed to delete user", e);
} finally {
em.close();
}
}
// Batch operations
public void batchCreateUsers(List<User> users) {
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
for (int i = 0; i < users.size(); i++) {
em.persist(users.get(i));
// Batch flush
if (i % 50 == 0) {
em.flush();
em.clear();
}
}
tx.commit();
} catch (Exception e) {
if (tx.isActive()) {
tx.rollback();
}
throw new RuntimeException("Failed to batch create users", e);
} finally {
em.close();
}
}
public void close() {
if (emf != null && emf.isOpen()) {
emf.close();
}
}
}
Advanced Features
// AdvancedQueries.java
package com.example.repository;
import javax.persistence.*;
import javax.persistence.criteria.*;
import java.util.List;
import java.util.Map;
public class AdvancedQueries {
private EntityManagerFactory emf;
public AdvancedQueries() {
this.emf = Persistence.createEntityManagerFactory("BlogPU");
}
// Using Criteria API
public List<User> searchUsers(String name, Integer minAge, Integer maxAge) {
EntityManager em = emf.createEntityManager();
try {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<User> cq = cb.createQuery(User.class);
Root<User> user = cq.from(User.class);
Predicate predicate = cb.conjunction();
if (name != null) {
predicate = cb.and(predicate,
cb.like(user.get("name"), "%" + name + "%"));
}
if (minAge != null) {
predicate = cb.and(predicate,
cb.greaterThanOrEqualTo(user.get("age"), minAge));
}
if (maxAge != null) {
predicate = cb.and(predicate,
cb.lessThanOrEqualTo(user.get("age"), maxAge));
}
cq.where(predicate);
cq.orderBy(cb.asc(user.get("name")));
TypedQuery<User> query = em.createQuery(cq);
return query.getResultList();
} finally {
em.close();
}
}
// JOIN operations
public List<Object[]> getUsersWithPostCount() {
EntityManager em = emf.createEntityManager();
try {
String jpql = "SELECT u, COUNT(p) FROM User u " +
"LEFT JOIN u.posts p " +
"GROUP BY u " +
"ORDER BY COUNT(p) DESC";
TypedQuery<Object[]> query = em.createQuery(jpql, Object[].class);
return query.getResultList();
} finally {
em.close();
}
}
// Native SQL queries
public List<Map<String, Object>> getStatistics() {
EntityManager em = emf.createEntityManager();
try {
String sql = "SELECT " +
"COUNT(DISTINCT u.id) as user_count, " +
"COUNT(DISTINCT p.id) as post_count, " +
"AVG(u.age) as avg_age " +
"FROM users u " +
"LEFT JOIN posts p ON u.id = p.author_id";
Query query = em.createNativeQuery(sql);
@SuppressWarnings("unchecked")
List<Object[]> results = query.getResultList();
// Convert results to Map
return results.stream()
.map(row -> Map.of(
"userCount", row[0],
"postCount", row[1],
"avgAge", row[2]
))
.toList();
} finally {
em.close();
}
}
// Fetch strategy control
public User getUserWithPosts(Long userId) {
EntityManager em = emf.createEntityManager();
try {
// Eager load with fetch join
String jpql = "SELECT DISTINCT u FROM User u " +
"LEFT JOIN FETCH u.posts " +
"WHERE u.id = :userId";
TypedQuery<User> query = em.createQuery(jpql, User.class);
query.setParameter("userId", userId);
return query.getSingleResult();
} catch (NoResultException e) {
return null;
} finally {
em.close();
}
}
// Optimistic locking
@Entity
@Table(name = "versioned_entities")
public static class VersionedEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String data;
@Version
private Long version;
// ... getters and setters
}
public void updateWithOptimisticLock(VersionedEntity entity) {
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
em.merge(entity);
tx.commit();
} catch (OptimisticLockException e) {
if (tx.isActive()) {
tx.rollback();
}
throw new RuntimeException("Entity was modified by another transaction", e);
} finally {
em.close();
}
}
}
Practical Example
// Spring Boot integration example
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
@Repository
@Transactional
public class SpringJpaUserRepository {
@PersistenceContext
private EntityManager em;
public User save(User user) {
if (user.getId() == null) {
em.persist(user);
return user;
} else {
return em.merge(user);
}
}
public Optional<User> findById(Long id) {
return Optional.ofNullable(em.find(User.class, id));
}
public List<User> findByNameContaining(String keyword) {
String jpql = "SELECT u FROM User u WHERE u.name LIKE :keyword";
TypedQuery<User> query = em.createQuery(jpql, User.class);
query.setParameter("keyword", "%" + keyword + "%");
return query.getResultList();
}
// Pagination
public Page<User> findAll(int page, int size) {
// Count query
String countJpql = "SELECT COUNT(u) FROM User u";
Long total = em.createQuery(countJpql, Long.class).getSingleResult();
// Data retrieval query
String jpql = "SELECT u FROM User u ORDER BY u.name";
TypedQuery<User> query = em.createQuery(jpql, User.class);
query.setFirstResult(page * size);
query.setMaxResults(size);
List<User> content = query.getResultList();
return new Page<>(content, page, size, total);
}
// Bulk update
public int deactivateOldUsers(LocalDateTime before) {
String jpql = "UPDATE User u SET u.active = false " +
"WHERE u.createdAt < :before AND u.active = true";
Query query = em.createQuery(jpql);
query.setParameter("before", before);
return query.executeUpdate();
}
}
// Service layer
@Service
@Transactional
public class BlogService {
@Autowired
private SpringJpaUserRepository userRepository;
public void createBlogPost(Long userId, String title, String content) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new EntityNotFoundException("User not found"));
Post post = new Post();
post.setTitle(title);
post.setContent(content);
post.setAuthor(user);
post.setCreatedAt(LocalDateTime.now());
user.getPosts().add(post);
userRepository.save(user);
}
@Transactional(readOnly = true)
public List<PostDTO> getUserPosts(Long userId) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new EntityNotFoundException("User not found"));
return user.getPosts().stream()
.map(post -> new PostDTO(
post.getId(),
post.getTitle(),
post.getContent(),
post.getCreatedAt()
))
.toList();
}
}
// DTO class
public class PostDTO {
private Long id;
private String title;
private String content;
private LocalDateTime createdAt;
// constructor, getters, setters...
}
// Paging helper
public class Page<T> {
private List<T> content;
private int page;
private int size;
private long totalElements;
public Page(List<T> content, int page, int size, long totalElements) {
this.content = content;
this.page = page;
this.size = size;
this.totalElements = totalElements;
}
public int getTotalPages() {
return (int) Math.ceil((double) totalElements / size);
}
// getters...
}