A practical walkthrough of SQL-First persistence: no XML, no Mapper interfaces, no generated queries.
I maintain a Java backend that handles ~1M requests/day. For persistence, we used to run MyBatis. The XML was manageable at first, then it wasn't. Dynamic conditions became <if> tag soup. A simple join query needed three files and two languages.
We switched to a simpler approach. Here's how it works for the most common case: single-table CRUD.
What You Need
- Java 21+
- Spring Boot (any 3.x)
- A database (H2 for the demo, MySQL/PostgreSQL for production)
That's it. No XML parser, no code generator, no annotation processor.
The Project
pom.xml
src/main/java/example/
DemoApplication.java
user/
User.java -- entity
UserDao.java -- data access
UserCond.java -- query conditions
src/main/resources/
application.yml
schema.sql
Three Java files for a complete CRUD API.
The Entity
@Data @Builder
@Table("sys_user")
public class User {
@Id private Long id;
private String name;
private Integer age;
private String email;
// ... other fields
// These four are auto-managed:
private LocalDateTime createTime;
private Long createBy;
private LocalDateTime updateTime;
private Long updateBy;
private Byte dr; // 0 = active, 1 = soft-deleted
}
@Table maps to the database table. @Id marks the primary key (Snowflake ID by default). The audit fields and soft-delete marker are handled automatically—you don't set them in business code.
The DAO
@Repository
public class UserDao extends BaseDao<User> {
// Empty. All CRUD methods inherited.
}
BaseDao provides save, saveBatch, update, delete, findById, list, page, count, exists. For single-table operations, this is all you need.
The Conditions
@Getter @Setter @Builder
public class UserCond extends BaseCondition {
private String name;
private Integer ageMin;
private Integer ageMax;
private Byte dr;
private Object[] ids;
@Override
protected void addCondition() {
and("name LIKE", name, 3); // 3 = %value%
and("age >=", ageMin);
and("age <=", ageMax);
and("dr =", dr);
in("id", ids);
}
}
Each and() line is one condition. If the parameter is null, the condition is skipped. No <if> tags, no OGNL, no XML.
The 3 in and("name LIKE", name, 3) means "wrap with % on both sides". 1 = suffix, 2 = prefix.
Running It
@Autowired UserDao userDao;
public void demo() {
// Insert
User user = User.builder().name("John").age(25).email("john@example.com").build();
userDao.save(user); // id, createTime, createBy, dr auto-filled
// Query by ID
User found = userDao.findById(user.getId());
// Paginated search
Page<User> page = userDao.page(
UserCond.builder().name("John").ageMin(20).ageMax(30).build()
);
// Update
found.setAge(26);
userDao.update(found); // updateTime, updateBy auto-filled
// Soft delete (dr=1, not DELETE)
userDao.delete(found.getId());
}
The SQL (from logs)
-- save()
INSERT INTO sys_user (id,name,age,email,create_time,create_by,dr)
VALUES (3679201737291333632,'John',25,'john@example.com','2026-04-10 13:14:10',1000,0)
-- page()
SELECT COUNT(1) FROM sys_user t
WHERE t.name LIKE '%John%' AND t.age >= 20 AND t.age <= 30
SELECT t.id,t.name,t.age,t.email FROM sys_user t
WHERE t.name LIKE '%John%' AND t.age >= 20 AND t.age <= 30
LIMIT 0,10
-- delete() with dr field present
UPDATE sys_user t SET dr=1 WHERE id IN (3679201737291333632)
The SQL in the logs is the SQL that runs. No hidden transformations, no proxy-generated queries.
What This Isn't
- Not an ORM: We don't map object graphs or handle relationships automatically. For joins, you write the SQL.
- Not type-safe SQL: If you misspell a column name, you find out at runtime. The tradeoff is transparency.
- Not a replacement for everything: If you need complex caching, second-level cache, or distributed transactions, Spring has other tools.
What This Is
A thin bridge between Java and SQL. The framework handles:
- Parameter collection and null-checking
- Audit field auto-fill
- Soft-delete logic
- Pagination (count + limit)
You handle:
- The SQL
- The business logic
- The optimization
Next
Episode 02: Multi-table joins with the same API → [link]
Full source: github.com/gzz2017gzz/simple-dao-demo