常见问题解答 - 独立使用
使用 LCGYL Framework 独立构建应用时的常见问题。
如果你在使用 Spring 集成,请查看 Spring 集成常见问题。
安装和配置
Q: 需要什么版本的 JDK?
A: LCGYL Framework 需要 JDK 21 或更高版本。框架使用了 Java 21 的新特性,如 Virtual Threads、Record、Sealed Classes 等。
bash
# 检查 Java 版本
java -version
# 应该显示类似:
# java version "21.0.0" 2023-09-19 LTSQ: 如何配置多数据源?
A: 在配置文件中定义多个数据源,然后在代码中分别创建:
properties
# 主数据源
datasource.primary.url=jdbc:mysql://localhost:3306/db1
datasource.primary.username=root
datasource.primary.password=password
# 从数据源
datasource.secondary.url=jdbc:mysql://localhost:3306/db2
datasource.secondary.username=root
datasource.secondary.password=passwordjava
@Component
public class DataSourceConfig {
@Inject
private Configuration config;
public DataSource primaryDataSource() {
return DataSourceBuilder.create()
.url(config.getString("datasource.primary.url"))
.username(config.getString("datasource.primary.username"))
.password(config.getString("datasource.primary.password"))
.build();
}
public DataSource secondaryDataSource() {
return DataSourceBuilder.create()
.url(config.getString("datasource.secondary.url"))
.username(config.getString("datasource.secondary.username"))
.password(config.getString("datasource.secondary.password"))
.build();
}
}Q: 如何切换不同环境的配置?
A: 使用环境配置文件:
config/
├── application.properties # 通用配置
├── application-dev.properties # 开发环境
├── application-test.properties # 测试环境
└── application-prod.properties # 生产环境激活环境:
bash
# 方式1:环境变量
export LCGYL_PROFILE=prod
# 方式2:命令行参数
java -jar app.jar --profile=prod
# 方式3:代码指定
ConfigurationManager.setActiveProfile("prod");依赖注入
Q: 构造器注入和字段注入有什么区别?
A: 推荐使用构造器注入:
java
// ✅ 推荐:构造器注入
@Component
public class UserService {
private final UserRepository userRepository;
@Inject
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
// ❌ 不推荐:字段注入
@Component
public class UserService {
@Inject
private UserRepository userRepository;
}优点:
- 依赖明确,易于测试
- 保证依赖不为 null
- 支持 final 字段
- 便于单元测试
Q: 如何解决循环依赖?
A: 避免循环依赖的方法:
- 重构代码(推荐):
java
// 将共同依赖提取到新的服务中
@Component
public class CommonService {
// 共同逻辑
}
@Component
public class ServiceA {
@Inject
private CommonService commonService;
}
@Component
public class ServiceB {
@Inject
private CommonService commonService;
}- 使用事件:
java
@Component
public class ServiceA {
@Inject
private EventBus eventBus;
public void doSomething() {
// 发布事件而不是直接调用 ServiceB
eventBus.publish(new SomeEvent());
}
}
@Component
public class ServiceB {
@EventListener
public void onEvent(SomeEvent event) {
// 处理事件
}
}Q: 如何注入接口的多个实现?
A: 使用 List 注入:
java
public interface PaymentService {
void pay(Order order);
}
@Component("alipayService")
public class AlipayService implements PaymentService {
// 支付宝实现
}
@Component("wechatPayService")
public class WechatPayService implements PaymentService {
// 微信支付实现
}
@Component
public class OrderService {
private final List<PaymentService> paymentServices;
@Inject
public OrderService(List<PaymentService> paymentServices) {
this.paymentServices = paymentServices;
}
public void pay(Order order, String paymentMethod) {
PaymentService service = paymentServices.stream()
.filter(s -> s.supports(paymentMethod))
.findFirst()
.orElseThrow();
service.pay(order);
}
}事务管理
Q: 事务不生效怎么办?
A: 检查以下几点:
- 确保方法是 public:
java
// ✅ 正确
@Transactional
public void saveUser(User user) { }
// ❌ 错误:private 方法
@Transactional
private void saveUser(User user) { }- 避免自调用:
java
// ❌ 错误:自调用不会触发事务
@Component
public class UserService {
public void register(User user) {
this.saveUser(user); // 自调用,事务不生效
}
@Transactional
public void saveUser(User user) {
userRepository.save(user);
}
}
// ✅ 正确:通过代理调用
@Component
public class UserService {
@Inject
private UserService self; // 注入自己
public void register(User user) {
self.saveUser(user); // 通过代理调用
}
@Transactional
public void saveUser(User user) {
userRepository.save(user);
}
}- 检查异常类型:
java
// 默认只回滚 RuntimeException
@Transactional
public void saveUser(User user) throws Exception {
// Exception 不会回滚
}
// 指定回滚异常
@Transactional(rollbackFor = Exception.class)
public void saveUser(User user) throws Exception {
// Exception 也会回滚
}Q: 如何在事务中调用异步方法?
A: 事务方法中不应直接调用异步方法,应该在事务提交后执行:
java
@Component
public class UserService {
@Inject
private EventBus eventBus;
@Transactional
public void register(User user) {
// 保存用户
userRepository.save(user);
// 发布事件,在事务提交后异步处理
eventBus.publishAsync(new UserRegisteredEvent(user));
}
}
@Component
public class EmailService {
@EventListener(async = true)
public void sendWelcomeEmail(UserRegisteredEvent event) {
// 异步发送邮件
emailSender.send(event.user().getEmail(), "欢迎注册");
}
}性能优化
Q: 如何优化数据库查询性能?
A: 几个优化建议:
- 使用批量操作:
java
// ❌ 不好:循环查询
for (Long id : ids) {
User user = userRepository.findById(id);
// ...
}
// ✅ 好:批量查询
List<User> users = userRepository.findByIds(ids);- 使用缓存:
java
@Component
public class UserService {
private final Cache<Long, User> cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
public User findById(Long id) {
return cache.get(id, () -> userRepository.findById(id));
}
}- 避免 N+1 查询:
java
// ❌ 不好:N+1 查询
List<Order> orders = orderRepository.findAll();
for (Order order : orders) {
User user = userRepository.findById(order.getUserId()); // N 次查询
order.setUser(user);
}
// ✅ 好:JOIN 查询
List<Order> orders = orderRepository.findAllWithUser();Q: 如何处理大量数据?
A: 使用分页和流式处理:
java
// 分页处理
int pageSize = 1000;
int page = 0;
Page<User> userPage;
do {
userPage = userRepository.findAll(page, pageSize);
for (User user : userPage.getContent()) {
// 处理用户
processUser(user);
}
page++;
} while (userPage.hasNext());
// 流式处理
jdbcTemplate.query(
"SELECT * FROM users",
rs -> {
while (rs.next()) {
User user = mapUser(rs);
processUser(user);
}
}
);安全
Q: 如何防止 SQL 注入?
A: 始终使用参数化查询:
java
// ✅ 正确:参数化查询
String sql = "SELECT * FROM users WHERE username = ?";
User user = jdbcTemplate.queryForObject(sql, User.class, username);
// ❌ 错误:字符串拼接
String sql = "SELECT * FROM users WHERE username = '" + username + "'";
User user = jdbcTemplate.queryForObject(sql, User.class);Q: 如何存储密码?
A: 使用加密算法(如 BCrypt):
java
@Component
public class UserService {
@Inject
private PasswordEncoder passwordEncoder;
public void register(String username, String password) {
// 加密密码
String encodedPassword = passwordEncoder.encode(password);
User user = new User();
user.setUsername(username);
user.setPassword(encodedPassword);
userRepository.save(user);
}
public boolean login(String username, String password) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new BusinessException("用户不存在"));
// 验证密码
return passwordEncoder.matches(password, user.getPassword());
}
}部署
Q: 如何打包应用?
A: 使用 Gradle 打包:
bash
# 构建 JAR
./gradlew build
# 生成的 JAR 在 build/libs/ 目录
java -jar build/libs/app.jarQ: 如何配置日志?
A: 在 logback.xml 中配置:
xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</configuration>更多问题
如果你的问题没有在这里找到答案,可以: