Skip to content

常见问题解答 - 独立使用

使用 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 LTS

Q: 如何配置多数据源?

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=password
java
@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: 避免循环依赖的方法:

  1. 重构代码(推荐):
java
// 将共同依赖提取到新的服务中
@Component
public class CommonService {
    // 共同逻辑
}

@Component
public class ServiceA {
    @Inject
    private CommonService commonService;
}

@Component
public class ServiceB {
    @Inject
    private CommonService commonService;
}
  1. 使用事件
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: 检查以下几点:

  1. 确保方法是 public
java
// ✅ 正确
@Transactional
public void saveUser(User user) { }

// ❌ 错误:private 方法
@Transactional
private void saveUser(User user) { }
  1. 避免自调用
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);
    }
}
  1. 检查异常类型
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: 几个优化建议:

  1. 使用批量操作
java
// ❌ 不好:循环查询
for (Long id : ids) {
    User user = userRepository.findById(id);
    // ...
}

// ✅ 好:批量查询
List<User> users = userRepository.findByIds(ids);
  1. 使用缓存
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));
    }
}
  1. 避免 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.jar

Q: 如何配置日志?

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>

更多问题

如果你的问题没有在这里找到答案,可以:

Released under the Apache License 2.0