常见问题(FAQ)

本文档收集了 Unabo 框架使用过程中的常见问题和解决方案。

目录


安装与配置

Q1: 如何添加 Unabo 依赖?

A: Maven 项目添加以下依赖:

<dependency>
    <groupId>online.sanen</groupId>
    <artifactId>unabo</artifactId>
    <version>1.2.4</version>
</dependency>

Gradle 项目:

implementation 'online.sanen:unabo:1.2.4'

Q2: 需要哪些依赖?

A: 除了 Unabo 本身,还需要:

  • 数据库驱动(必选):根据使用的数据库选择

    • MySQL:mysql-connector-java
    • PostgreSQL:postgresql
    • Oracle:ojdbc6
    • SQL Server:jtds
    • SQLite:sqlite-jdbc(已包含)
    • 达梦:DmJdbcDriver18
    • MongoDB:mongodb-driver-sync
  • 连接池(可选,默认使用 Dbcp)

    • HikariCP(推荐):HikariCP
    • Druid:druid
    • C3P0:c3p0
  • Spring Boot 集成(可选)

    • spring-data-jdbc(用于 Spring 事务)

Q3: 如何关闭 SQL 日志?

A: 在配置中设置:

Bootstrap bootstrap = Unabo.load(configuration -> {
    configuration.setUrl("jdbc:mysql://localhost:3306/test");
    configuration.setDriverOption(DriverOption.MYSQL_CJ);
    configuration.setIsShowSql(false);  // 关闭 SQL 日志
});

或在 application.yml 中:

unabo:
  sql-instances:
    - id: sys
      show-log: false  # 关闭日志

Q4: 如何配置连接池参数?

A: 方式一:使用配置对象

Bootstrap bootstrap = Unabo.load(configuration -> {
    configuration.setUrl("jdbc:mysql://localhost:3306/test");
    configuration.setDriverOption(DriverOption.MYSQL_CJ);
    configuration.setUsername("root");
    configuration.setPassword("123456");
    configuration.setDatasourceType(Configuration.DatasourceType.HikariCP);
    configuration.setMaxActive(50);  // 最大连接数
});

方式二:获取连接池实例深度配置

import com.zaxxer.hikari.HikariDataSource;

HikariDataSource dataSource = (HikariDataSource) bootstrap.manager()
    .getTemplate()
    .getDataSource();

dataSource.setMaximumPoolSize(50);
dataSource.setMinimumIdle(10);

基本使用

Q5: 如何创建 Bootstrap 实例?

A: 三种方式:

方式一:无 ID(不缓存)

Bootstrap bootstrap = Unabo.load(configuration -> {
    configuration.setUrl("jdbc:mysql://localhost:3306/test");
    configuration.setDriverOption(DriverOption.MYSQL_CJ);
    configuration.setUsername("root");
    configuration.setPassword("123456");
});

方式二:有 ID(缓存)

Bootstrap bootstrap = Unabo.load("sys", configuration -> {
    // 配置...
});

方式三:try-with-resources(推荐)

try (Bootstrap bootstrap = Unabo.load(configuration -> {
    // 配置...
})) {
    // 使用 bootstrap
} // 自动关闭

Q6: 如何创建表?

A: 方式一:使用 Map

Map<String, Object> schema = new HashMap<>();
schema.put("id", 1);
schema.put("name", "");
schema.put("age", 0);
bootstrap.queryMap("user", schema).setPrimary("id").create();

方式二:使用实体类

@Table(name = "user")
public class User {
    @Id
    private Integer id;
    private String name;
    private Integer age;
}

bootstrap.query(new User()).createTable();

Q7: 如何指定字段类型?

A: 在 Map 中设置对应类型的示例值:

Map<String, Object> schema = new HashMap<>();
schema.put("id", 1);           // Integer
schema.put("name", "");        // String
schema.put("age", 0);          // Integer
schema.put("price", 0.0);      // Double
schema.put("active", true);    // Boolean
schema.put("created_at", new Date());  // Date
bootstrap.queryMap("product", schema).setPrimary("id").create();

Q8: 如何插入数据?

A: 方式一:使用 Map

Map<String, Object> user = new HashMap<>();
user.put("name", "张三");
user.put("age", 25);
bootstrap.queryMap("user", user).insert();

方式二:使用实体类

User user = new User();
user.setName("李四");
user.setAge(30);
bootstrap.query(user).insert();

方式三:批量插入

List<User> users = Arrays.asList(user1, user2, user3);
bootstrap.query(users).insert();

查询相关

Q9: 如何查询所有数据?

A:

// 返回 Map 列表
List<Map<String, Object>> users = bootstrap.queryTable("user").maps();

// 返回实体列表
List<User> users = bootstrap.queryTable("user").list(User.class);

Q10: 如何根据条件查询?

A:

import online.sanen.unabo.api.condition.C;

// 单条件
List<User> users = bootstrap.queryTable("user")
    .addCondition(C.eq("age", 25))
    .list(User.class);

// 多条件(AND)
List<User> users = bootstrap.queryTable("user")
    .addCondition(C.eq("age", 25))
    .addCondition(C.like("name", "张%"))
    .list(User.class);

// OR 条件
List<User> users = bootstrap.queryTable("user")
    .addCondition(C.or(
        C.eq("age", 25),
        C.eq("age", 30)
    ))
    .list(User.class);

Q11: 如何分页查询?

A:

import com.mhdt.structure.Page;

int currentPage = 1;
int pageSize = 10;

// 查询总数
int total = bootstrap.queryTable("user").count();

// 创建分页对象
Page page = new Page(currentPage, pageSize);
page.setCount(total);

// 分页查询
List<User> users = bootstrap.queryTable("user")
    .limit(page.skip(), pageSize)
    .sort(Sorts.DESC, "id")
    .list(User.class);

Q12: 如何只查询部分字段?

A:

// 指定查询字段
List<Map<String, Object>> users = bootstrap.queryTable("user")
    .setFields("id", "name", "age")
    .maps();

// 排除某些字段
List<Map<String, Object>> users = bootstrap.queryTable("user")
    .setExceptFields("password", "secret")
    .maps();

Q13: 如何处理查询结果为空?

A:

// 方式一:使用 Optional
Optional<User> user = bootstrap.queryTable("user")
    .addCondition(C.eq("id", 1))
    .unique(User.class);

if (user.isPresent()) {
    User u = user.get();
    // 处理用户
} else {
    // 处理为空情况
}

// 方式二:使用 orElse
User user = bootstrap.queryTable("user")
    .addCondition(C.eq("id", 1))
    .unique(User.class)
    .orElse(null);

Q14: 条件值为 null 怎么办?

A:

// 方式一:动态添加条件
bootstrap.queryTable("user").addCondition(conds -> {
    if (name != null) {
        conds.add(C.like("name", name));
    }
    if (age != null) {
        conds.add(C.eq("age", age));
    }
}).list();

// 方式二:使用 enable
bootstrap.queryTable("user")
    .addCondition(C.like("name", name).enable(name != null))
    .addCondition(C.eq("age", age).enable(age != null))
    .list();

Q15: 如何排序?

A:

// 单字段排序
List<User> users = bootstrap.queryTable("user")
    .sort(Sorts.DESC, "id")
    .list(User.class);

// 多字段排序
List<User> users = bootstrap.queryTable("user")
    .sort(Sorts.DESC, "age")
    .sort(Sorts.ASC, "id")
    .list(User.class);

事务相关

Q16: 如何开启事务?

A: 三种方式:

方式一:JDBC 事务

import online.sanen.unabo.api.structure.enums.TransactionFactoryEnum;

Bootstrap bootstrap = Unabo.load(configuration -> {
    // 配置...
    configuration.setTransactionFactory(TransactionFactoryEnum.JdbcTransactionFactory);
});

// 使用事务
Unabo.openSession(() -> {
    bootstrap.query(user).insert();
    bootstrap.query(order).insert();
}, bootstrap);

方式二:Spring 事务

# application.yml
unabo:
  sql-instances:
    - id: sys
      transaction: SpringManagedTransactionFactory
import org.springframework.transaction.annotation.Transactional;

@Transactional
public void method() {
    // 数据库操作
}

方式三:MongoDB 事务

# application.yml
unabo:
  nosql-instances:
    - id: mongodb
      transaction-factory: MongoDBTransactionFactory

Q17: Spring 事务和多数据源如何处理?

A: Spring 事务只支持单数据源回滚,多数据源场景必须使用 JDBC 事务:

// ✅ 正确:使用 JDBC 事务
Unabo.openSession(() -> {
    bootstrap1.query(user).insert();
    bootstrap2.query(order).insert();
}, bootstrap1, bootstrap2);

// ❌ 错误:Spring 事务无法保证多数据源同时回滚
@Transactional
public void method() {
    bootstrap1.query(user).insert();
    bootstrap2.query(order).insert();
}

Q18: 如何手动回滚事务?

A:

try {
    bootstrap.openSession();
    
    // 数据库操作
    bootstrap.query(user).insert();
    
    // 手动回滚
    if (someCondition) {
        bootstrap.rollback();
        return;
    }
    
    // 提交
    bootstrap.commit();
    
} catch (Exception e) {
    try {
        bootstrap.rollback();
    } catch (SQLException e1) {
        e1.printStackTrace();
    }
}

性能相关

Q19: 如何提升查询性能?

A:

  1. 只查询需要的字段

    bootstrap.queryTable("user")
        .setFields("id", "name")
        .maps();
    
  2. 使用索引字段作为查询条件

  3. 避免 N+1 查询

    // ❌ 不好
    for (Order order : orders) {
        User user = bootstrap.queryTable("user")
            .addCondition(C.eq("id", order.getUserId()))
            .unique()
            .orElse(null);
    }
    
    // ✅ 好
    Set<Integer> userIds = orders.stream()
        .map(Order::getUserId)
        .collect(Collectors.toSet());
    List<User> users = bootstrap.queryTable("user")
        .addCondition(C.in("id", userIds))
        .list();
    
  4. 使用批量操作

    // 批量插入
    bootstrap.query(users).insert();
    
  5. 合理使用分页

    bootstrap.queryTable("user")
        .limit(0, 100)
        .list();
    

Q20: 如何使用批量操作?

A:

// 批量插入
List<User> users = Arrays.asList(user1, user2, user3);
bootstrap.query(users).insert();

// 批量更新
bootstrap.query(users).update();

// 分批处理大数量
int batchSize = 500;
for (int i = 0; i < allUsers.size(); i += batchSize) {
    int end = Math.min(i + batchSize, allUsers.size());
    List<User> batch = allUsers.subList(i, end);
    bootstrap.query(batch).insert();
}

Q21: 如何开启缓存?

A:

WARNING

缓存默认关闭,建议只在只读数据上使用,否则可能导致脏数据。

Bootstrap bootstrap = Unabo.load(configuration -> {
    configuration.setUrl("jdbc:mysql://localhost:3306/test");
    configuration.setCache(true);  // 开启缓存
});

// 或只缓存特定表
bootstrap.queryTable("sys_config")
    .enableCache(true)
    .maps();

Q22: 如何使用流式处理大数据?

A:

使用 stream() 方法分批处理大数据,避免一次性加载到内存:

// 方式1:按批次处理
bootstrap.queryTable("user").stream(1000, rows -> {
    // 每批最多 1000 条数据
    for (Map<String, Object> row : rows) {
        User user = new User();
        user.setId((Integer) row.get("id"));
        user.setName((String) row.get("name"));
        processUser(user);
    }
});

// 方式2:逐行处理 + 批次回调
bootstrap.queryTable("user").stream(1000,
    row -> {
        // 逐行处理
        User user = new User();
        user.setId((Integer) row.get("id"));
        user.setName((String) row.get("name"));
        processUser(user);
    },
    rows -> {
        // 每批处理完成后的回调
        System.out.println("完成一批: " + rows.size() + " 条");
    }
);

参数说明:

  • bufferSize: 批次大小,每批处理的记录数
  • rowProcess: 逐行处理回调(可选)
  • consumer: 批次处理回调

Spring Boot 集成

Q23: 如何在 Spring Boot 中使用 Unabo?

A:

步骤1:添加依赖

<dependency>
    <groupId>online.sanen</groupId>
    <artifactId>unabo</artifactId>
    <version>1.2.4</version>
</dependency>

步骤2:配置 application.yml

unabo:
  enable: true
  sql-instances:
    - id: sys
      driver-option: mysql-cj
      datasource-type: hikaricp
      url: jdbc:mysql://localhost:3306/test
      username: root
      password: 123456

步骤3:排除默认数据源

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

步骤4:注入 Bootstrap

import javax.annotation.Resource;

@Service
public class UserService {
    
    @Resource
    private Bootstrap bootstrap;
    
    public List<User> getAllUsers() {
        return bootstrap.queryTable("user").list(User.class);
    }
}

Q24: 多数据源如何配置?

A:

unabo:
  enable: true
  sql-instances:
    - id: db1
      url: jdbc:mysql://localhost:3306/db1
      username: root
      password: 123456
    - id: db2
      url: jdbc:mysql://localhost:3306/db2
      username: root
      password: 123456
@Service
public class MultiDataSourceService {
    
    @Resource
    private Bootstrap db1;  // Bean 名为配置的 id
    
    @Resource
    private Bootstrap db2;
    
    public void method() {
        db1.queryTable("user").list();
        db2.queryTable("order").list();
    }
}

Q25: Spring 事务不生效怎么办?

A: 检查以下几点:

  1. 是否配置了 Spring 事务工厂
unabo:
  sql-instances:
    - id: sys
      transaction: SpringManagedTransactionFactory  # 必须配置
  1. 是否启用了事务管理
@EnableTransactionManagement
public class Application {
    // ...
}
  1. 是否排除了默认数据源
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class Application {
    // ...
}
  1. 是否添加了 @Transactional 注解
@Transactional
public void method() {
    // 数据库操作
}
  1. 是否在同一个类中调用
// ❌ 不会生效(内部调用)
public void method1() {
    method2();
}

@Transactional
public void method2() {
    // 事务代码
}

// ✅ 会生效(外部调用)
@Service
public class ServiceA {
    @Resource
    private ServiceB serviceB;
    
    public void method1() {
        serviceB.method2();
    }
}

@Service
public class ServiceB {
    @Transactional
    public void method2() {
        // 事务代码
    }
}

MongoDB 相关

Q26: 如何使用 MongoDB?

A:

import online.sanen.unabo.nosql.Bootstrap;

// 创建 MongoDB 实例
Bootstrap bootstrap = Unabo.nosql.load(configurationNosql -> {
    configurationNosql.setIp("127.0.0.1");
    configurationNosql.setPort(27017);
    configurationNosql.setSchema("test");
    configurationNosql.setUsername("root");
    configurationNosql.setPassword("123456");
});

// 插入文档
Map<String, Object> doc = new HashMap<>();
doc.put("name", "张三");
doc.put("age", 25);
bootstrap.queryMap("user", doc).insert();

// 查询文档
List<Map<String, Object>> users = bootstrap.queryTable("user").maps();

Q27: MongoDB 如何使用聚合管道?

A:

// Unwind 展开
bootstrap.pipeline("user")
    .unwind("child")
    .project("id", "name", "age", "child.name", "child.age")
    .alias(Collections.asMap("child.name", "cName", "child.age", "cAge"))
    .addCondition(conds -> conds.add(gte("age", 18)))
    .maps();

// Group 分组
bootstrap.pipeline("group")
    .group(Collections.singletonList("name"), 
        Aggregation.newInstance().avg("score").first("sex"))
    .project("score_avg", "sex_first", "name")
    .alias(Collections.asMap("score_avg", "score", "sex_first", "sex"))
    .maps();

Q28: MongoDB 事务如何配置?

A:

unabo:
  enable: true
  nosql-instances:
    - id: mongodb
      ip: 127.0.0.1
      port: 27017
      username: root
      password: 123456
      schema: test
      transaction-factory: MongoDBTransactionFactory

WARNING

MongoDB 事务需要副本集配置,开发环境至少需要单副本节点。


错误排查

Q29: 连接数据库失败怎么办?

A: 检查以下几点:

  1. URL 是否正确
// MySQL
configuration.setUrl("jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC");

// PostgreSQL
configuration.setUrl("jdbc:postgresql://localhost:5432/test");

// Oracle
configuration.setUrl("jdbc:oracle:thin:@//localhost:1521/ORCL");
  1. 用户名和密码是否正确

  2. 数据库服务是否启动

  3. 防火墙是否开放端口

  4. 数据库驱动是否正确

configuration.setDriverOption(DriverOption.MYSQL_CJ);

Q30: 表不存在怎么办?

A:

// 检查表是否存在
if (bootstrap.dataInformation().containsTable("user")) {
    // 表存在,执行查询
} else {
    // 表不存在,创建表
    Map<String, Object> schema = new HashMap<>();
    schema.put("id", 1);
    schema.put("name", "");
    bootstrap.queryMap("user", schema).setPrimary("id").create();
}

Q31: 字段类型不匹配怎么办?

A:

// 方式一:关闭自动类型转换
bootstrap.queryTable("user")
    .addCondition(C.eq("age", "18").valueCastType(false))
    .list();

// 方式二:使用正确的类型
bootstrap.queryTable("user")
    .addCondition(C.eq("age", 18))  // Integer 类型
    .list();

Q32: 如何查看执行的 SQL?

A:

// 方式一:开启 SQL 日志
configuration.setIsShowSql(true);

// 方式二:格式化 SQL
configuration.setFormat(true);

Q33: 批量操作失败怎么办?

A: 检查以下几点:

  1. 数据是否正确
// 检查数据
users.forEach(user -> {
    if (user.getName() == null) {
        throw new RuntimeException("用户名不能为空");
    }
});
  1. 字段类型是否匹配

  2. 主键是否重复

  3. 批量大小是否合理

// 分批处理
int batchSize = 500;
for (int i = 0; i < allUsers.size(); i += batchSize) {
    int end = Math.min(i + batchSize, allUsers.size());
    List<User> batch = allUsers.subList(i, end);
    bootstrap.query(batch).insert();
}

Q34: 如何处理并发问题?

A:

// 方式一:使用乐观锁
@Table(name = "user")
public class User {
    @Id
    private Integer id;
    
    private String name;
    
    @Column(name = "version")
    private Integer version;  // 版本号
}

// 更新时检查版本
bootstrap.queryTable("user")
    .addCondition(C.eq("id", userId))
    .addCondition(C.eq("version", oldVersion))
    .update(Collections.singletonMap("version", oldVersion + 1));

// 方式二:使用数据库事务
Unabo.openSession(() -> {
    // 事务操作
}, bootstrap);

Q35: 内存溢出怎么办?

A:

// ❌ 不好:一次性加载所有数据
List<User> allUsers = bootstrap.queryTable("user").list();

// ✅ 好:使用分页
Page page = new Page(1, 100);
page.setCount(bootstrap.queryTable("user").count());
List<User> users = bootstrap.queryTable("user")
    .limit(page.skip(), 100)
    .list();

// 或使用流式处理
bootstrap.queryTable("user").stream(100, rows -> {
    for (Map<String, Object> row : rows) {
        User user = new User();
        // 处理用户
    }
});

其他问题

Q36: 如何获取帮助?

A:

  • 📧 邮箱:282854237@qq.com
  • 🌐 官网:http://unabo.sanen.online
  • 🐛 GitHub Issues:https://github.com/sanen-projects/unabo/issues

Q37: Unabo 和 MyBatis/JPA 有什么区别?

A:

特性UnaboMyBatisJPA
学习曲线简单中等复杂
SQL 灵活性很高
性能一般
Spring 集成很好很好
代码量中等
社区支持
MongoDB 支持原生需要额外配置需要 Spring Data MongoDB

Q38: Unabo 适合什么场景?

A:

推荐使用:

  • 中小型项目
  • 需要 SQL + MongoDB 混合使用
  • 快速原型开发
  • 学习和研究 ORM 原理
  • 对性能要求不是极端的场景

不推荐使用:

  • 大型企业级应用
  • 需要复杂关联查询的场景
  • 要求完善的监控和运维支持
  • 团队技术栈需要标准化(如 MyBatis、JPA)

Q39: 如何升级 Unabo 版本?

A:

  1. 修改 pom.xml 中的版本号
<dependency>
    <groupId>online.sanen</groupId>
    <artifactId>unabo</artifactId>
    <version>最新版本</version>
</dependency>
  1. 查看 更新日志 了解变更

  2. 测试兼容性

  3. 部署上线

Q40: 如何参与贡献?

A:

欢迎贡献!请按照以下步骤:

  1. Fork 项目仓库
  2. 创建特性分支
  3. 提交更改
  4. 推送到分支
  5. 创建 Pull Request

获取更多帮助

如果以上 FAQ 无法解决你的问题,请:

  1. 查看官方文档:http://unabo.sanen.online
  2. 查看更新日志:更新日志
  3. 提交 Issue:https://github.com/sanen-projects/unabo/issues
  4. 联系作者:282854237@qq.com

相关文档