A high-performance, annotation-based JDBC framework for Java that generates repository implementations at compile time. EasyJDBC provides a lightweight alternative to JPA with significantly better performance while maintaining familiar annotation-driven development.
- High Performance: Significantly faster than JPA for both insert and query operations
- Compile-time Code Generation: Repository implementations generated during compilation
- Annotation-driven: Familiar JPA-like annotations for easy adoption
- Spring Boot Integration: Seamless integration with Spring Boot and Spring Data
- Type Safety: Full compile-time type checking
- Lightweight: Minimal runtime overhead
- Modern Java Support: Built for Java 17+ with support for Records
- Advanced Features:
- Custom WHERE clause conditions with
@QuerySQL - Relationship mapping (
@JoinColumn) - Automatic ID generation with Snowflake algorithm
- Database schema and table auto-creation
- Unique constraints and indexes
- Virtual tables and inheritance
- Custom WHERE clause conditions with
Based on comprehensive performance tests comparing EasyJDBC with JPA:
| Parameter | Value |
|---|---|
| Dataset | 100,000 User + Device records |
| Iterations | 10 iterations average |
| Query | query with 400 iterations (with JPA FetchType.LAZY) |
| Database | H2 in-memory (PostgreSQL Mode) |
| Environment | Spring Boot 3.5.3, Java 17 |
| Test Type | Operation | EasyJDBC Avg | JPA Avg | Performance Gain |
|---|---|---|---|---|
| Insert | 100K User + Device records | ~1477ms | ~2345ms | 1.5x faster |
| Query | 400 iterations | ~2140ms | ~4358ms | 2.0x faster |
Run the performance tests yourself:
./gradlew test --tests "*PerformanceTest*"Performance results may vary based on database configuration, hardware, and data complexity. Tests are conducted using H2 in-memory database.
EasyJDBC uses annotation processing to generate repository implementations at compile time:
- Entities: Annotated with
@Tableand persistence annotations - Repository Interfaces: Define method signatures
- Code Generation: Annotation processor generates implementations
- Runtime: Generated repositories use optimized JDBC operations
Add EasyJDBC to your build.gradle.kts:
dependencies {
// Required Spring Boot dependencies
implementation("org.springframework.boot:spring-boot-starter-data-jdbc")
// EasyJDBC util
compileOnly("com.wavjaby:easyjdbc:1.0-SNAPSHOT")
// Annotation processing
annotationProcessor("com.wavjaby:easyjdbc:1.0-SNAPSHOT")
}@Table(name = "USERS", repositoryClass = UsersRepository.class)
public record User(
@Id
@GenericGenerator(strategy = Snowflake.class)
long userId,
@Column(unique = true)
String username,
String password,
String[] email,
Timestamp registrationDate,
boolean active,
Long[] deviceIds
) {
}import com.wavjaby.persistence.Count;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import java.util.List;
public interface UsersRepository extends UserDetailsService {
User addUser(User user);
/**
* Spring Security UserDetailsService
* Match with username or any email in the array.
*/
@Override
User loadUserByUsername(@Where({"username", "email"}) String username);
List<User> getUsers();
List<User> findByNickname(@Where(ignoreCase = true, operation = "like") String username);
User getById(long userId);
boolean isUsernameExist(String username);
@Modifying
void updateUserPassword(@Where long id, @Where("password") String oldPassword, @FieldName("password") String newPassword);
@Delete
void deleteUser(long id);
@Count
int count();
}@SpringBootApplication
@ComponentScan(basePackages = {
"com.yourpackage",
"com.wavjaby.jdbc.util" // Include EasyJDBC utilities
})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public Snowflake snowflake() {
return new Snowflake(1, 1704067200000L); // workerId, epochTimestamp
}
}@Service
public class UserService {
@Autowired
private UsersRepository usersRepository;
public User createUser(String username, String password) {
User newUser = new User(
-1, // ID will be auto-generated
username,
password,
null, null, null,
null,
new Timestamp(System.currentTimeMillis()),
true,
null
);
return usersRepository.addUser(newUser);
}
public List<User> getAllUsers() {
return usersRepository.getUsers();
}
public User getUserByUsername(String username) {
return usersRepository.loadUserByUsername(username);
}
public int getUserCount() {
return usersRepository.count();
}
}@Table(schema = "device", repositoryClass = DeviceRepository.class)
public record Device(
@Id
@GenericGenerator(strategy = Snowflake.class)
long id,
@JoinColumn(referencedClass = User.class, referencedClassFieldName = "userId")
long ownerId,
@NotNull
@Column(name = "NAME_STR")
String deviceName,
@NotNull
@Column(precision = 10, scale = 6)
double numeric,
String description
) {}@Table- Mark entity class and specify repository@Column- Customize column mapping@Id- Mark primary key field@NotNull- Add NOT NULL constraint@ColumnDefault- Set default values@UniqueConstraint- Define unique constraints
@Select- Custom select queries@QuerySQL- Custom WHERE clause conditions@Where- Specify WHERE conditions@OrderBy- Add ORDER BY clauses@Limit- Limit result count@Count- Count operations
@Modifying- Mark update operations@Delete- Mark delete operations@UpdateData- Specify update data
@JoinColumn- Foreign key mapping
@GenericGenerator- Custom ID generation@FieldName- Custom field naming
public interface FriendRepository {
// Add custom WHERE conditions to queries
@QuerySQL("ACCEPT IS NULL")
List<Friend> getPendingRequests(long userId);
@QuerySQL("ACCEPT=TRUE")
boolean isFriend(@Where(value = {"userId", "friendId"}) long userIdA,
@Where(value = {"userId", "friendId"}) long userIdB);
@QuerySQL("(ACCEPT IS NULL OR ACCEPT=FALSE)")
List<Friend> getSentRequests(long userId);
@QuerySQL("ACCEPT=TRUE")
@Select(columnSql = """
case when USER_ID = :userId
then FRIEND_ID
else USER_ID
end as FRIEND_ID""")
List<Long> getFriendIds(@Where(value = {"userId", "friendId"}) long userId);
}@Table(
repositoryClass = DeviceSummary.Repository.class,
virtual = true,
virtualBaseClass = User.class
)
public record DeviceSummary(
@JoinColumn(referencedClass = Device.class, referencedClassFieldName = "ownerId")
long userId,
// Value from User
String username,
String email,
// Value from Device
int deviceName,
int description
) {
public interface Repository {
DeviceSummary getDeviceSummaryByOwnerId(long userId);
}
}EasyJDBC has been tested on H2 (PostgreSQL Mode) and verified to work with PostgreSQL in production environments.
# application.yml
spring:
datasource:
url: jdbc:h2:mem:testdb;MODE=PostgreSQL;DATABASE_TO_UPPER=FALSE;CASE_INSENSITIVE_IDENTIFIERS=TRUE
username: sa
password: ""
driver-class-name: org.h2.Driver# application.yml
spring:
datasource:
url: jdbc:postgresql://localhost:5432/yourdb
username: youruser
password: yourpassword
driver-class-name: org.postgresql.DriverEasyJDBC includes comprehensive performance tests. Run them with:
./gradlew test --tests "*PerformanceTest*"- Java: 17 or higher
- Spring Boot: 3.0 or higher
- Database: H2 (PostgreSQL Mode) or PostgreSQL 12+
- Build Tool: Gradle with annotation processing support
Database Compatibility: EasyJDBC is primarily developed and tested using PostgreSQL syntax. It is verified to work with:
- H2 Database (configured with
MODE=PostgreSQL) - PostgreSQL Server (12 or higher)
While it uses standard JDBC, features like array types and specific SQL generation are optimized for PostgreSQL compatibility.
| Feature | EasyJDBC | JPA |
|---|---|---|
| Performance | High | Moderate |
| Compile-time Safety | Full | Limited |
| Learning Curve | Easy (JPA-like) | Moderate |
| Runtime Overhead | Minimal | Heavy |
| Code Generation | Compile-time | Runtime |
| Custom WHERE Clauses | Easy | Complex |
| Relationship Mapping | Limited | Full |
| Caching | Manual | Built-in |
EasyJDBC - High-performance JDBC made easy!