车子的个人博客

正确的事情通常都是优雅的

Spring全家桶笔记六:数据访问层

Spring数据访问概述

Spring提供了统一的数据访问抽象,简化了JDBC、Hibernate、MyBatis等持久层框架的使用。

JDBC模板

JdbcTemplate基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Component
public class UserDao {

@Autowired
private JdbcTemplate jdbcTemplate;

public User findById(Long id) {
String sql = "SELECT * FROM user WHERE id = ?";
return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), id);
}

public List<User> findAll() {
String sql = "SELECT * FROM user";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
}

public int insert(User user) {
String sql = "INSERT INTO user(name, email) VALUES(?, ?)";
return jdbcTemplate.update(sql, user.getName(), user.getEmail());
}
}

命名参数

1
2
3
4
5
6
7
8
// 使用Map
Map<String, Object> params = new HashMap<>();
params.put("name", "张三");
params.put("email", "zhangsan@example.com");
namedParameterJdbcTemplate.update(sql, params);

// 使用BeanPropertySqlParameterSource
namedParameterJdbcTemplate.update(sql, new BeanPropertySqlParameterSource(user));

Spring Data JPA

快速开始

1
2
3
4
5
6
7
8
9
10
11
12
@Entity
@Table(name = "t_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;
private String email;

// getters and setters
}
1
2
3
4
5
6
public interface UserRepository extends JpaRepository<User, Long> {
// 自动实现CRUD
// 自定义查询
User findByName(String name);
List<User> findByEmailContaining(String email);
}

自定义查询

1
2
3
4
5
@Query("SELECT u FROM User u WHERE u.name = ?1")
User findByNameCustom(String name);

@Query(value = "SELECT * FROM user WHERE name = :name", nativeQuery = true)
User findByNameNative(@Param("name") String name);

复杂查询

1
2
3
4
5
6
7
8
9
10
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
}

Specification<User> spec = (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
if (name != null) {
predicates.add(cb.like(root.get("name"), "%" + name + "%"));
}
return cb.and(predicates.toArray(new Predicate[0]));
};

事务管理

声明式事务

1
2
3
4
5
6
7
8
9
10
11
@Service
public class UserService {

@Transactional(isolation = Isolation.READ_COMMITTED, timeout = 30)
public void transfer(Long fromId, Long toId, BigDecimal amount) {
// 扣款
accountDao.decrease(fromId, amount);
// 加款
accountDao.increase(toId, amount);
}
}

事务传播行为

传播行为 说明
REQUIRED 如果有事务则加入,没有则创建
REQUIRES_NEW 总是创建新事务
SUPPORTS 有事务则加入,没有则非事务
NOT_SUPPORTED 非事务执行
MANDATORY 必须有事务,否则抛异常
NEVER 必须没有事务,否则抛异常

常见问题

1. 懒加载异常

解决方案:开启spring.jpa.open-in-view(但影响性能)或使用EntityGraph

2. N+1问题

解决方案:使用JOIN FETCH或@EntityGraph

3. 乐观锁冲突

使用@Version注解实现乐观锁

总结

Spring数据访问层提供了强大的持久层支持,开发者可以根据项目需求选择合适的持久层方案。Spring Data JPA适合快速开发,JdbcTemplate适合需要精细控制SQL的场景。

JPA实体重复问题解决

问题

同一实体对象被多个EntityManager管理。

解决方式

1
2
3
4
5
6
7
8
9
// 方式1:merge
User user = entityManager.merge(user);

// 方式2:clear
entityManager.clear();

// 方式3:remove + persist
entityManager.remove(user);
entityManager.persist(user);

总结

正确管理实体生命周期可避免重复问题。

Spring全家桶笔记:核心概念与配置

Spring概述

Spring是一个轻量级的Java开发框架,旨在简化企业级应用开发。其核心是控制反转(IOC)面向切面编程(AOP)

IOC容器

什么是IOC

IOC(Inversion of Control)即控制反转,将对象创建和管理权交给Spring容器,由容器负责注入依赖。

容器实现

  • BeanFactory:基础容器,延迟加载
  • ApplicationContext:增强版容器,继承BeanFactory

Bean配置方式

1. XML配置

1
2
3
4
<bean id="userService" class="com.example.UserService">
<property name="userDao" ref="userDao"/>
<property name="name" value="张三"/>
</bean>

2. 注解配置

1
2
3
4
5
6
7
8
9
@Component
public class UserService {

@Autowired
private UserDao userDao;

@Value("${app.name}")
private String appName;
}

3. Java配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
public class AppConfig {

@Bean
public UserService userService() {
UserService service = new UserService();
service.setUserDao(userDao());
return service;
}

@Bean
public UserDao userDao() {
return new UserDaoImpl();
}
}

Bean作用域

作用域 说明
singleton 单例(默认)
prototype 多例
request HTTP请求
session HTTP会话
application ServletContext生命周期

生命周期

  1. 实例化 → 2. 属性赋值 → 3. 初始化 → 4. 销毁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Component
public class UserService implements InitializingBean, DisposableBean {

@PostConstruct
public void init() {
System.out.println("初始化");
}

@PreDestroy
public void destroy() {
System.out.println("销毁");
}

@Override
public void afterPropertiesSet() {}

@Override
public void destroy() {}
}

依赖注入

注入方式

  • 构造器注入:推荐,清晰表示依赖关系
  • Setter注入:可选注入
  • 字段注入:简洁但不利于测试
1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class OrderService {

private final UserService userService; // 构造器注入

@Autowired
private OrderDao orderDao; // 字段注入

public void setPaymentService(PaymentService paymentService) {
// Setter注入
}
}

自动装配

1
2
3
4
5
6
7
8
9
10
// byType:按类型自动装配
@Autowired
private UserDao userDao;

// byName:按名称自动装配
@Autowired
private UserDao userDaoImpl;

// 构造函数自动装配
public UserService(UserDao userDao) {}

AOP面向切面编程

概念

  • Aspect:切面
  • Join Point:连接点
  • Pointcut:切点
  • Advice:通知(增强)
  • Weaving:织入

通知类型

类型 说明
@Before 前置通知
@AfterReturning 后置通知
@AfterThrowing 异常通知
@After 最终通知
@Around 环绕通知

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Aspect
@Component
public class LoggingAspect {

@Pointcut("execution(* com.example.service.*.*(..))")
public void servicePointcut() {}

@Before("servicePointcut()")
public void before(JoinPoint joinPoint) {
System.out.println("方法: " + joinPoint.getSignature());
}

@AfterReturning(pointcut = "servicePointcut()", returning = "result")
public void afterReturning(Object result) {
System.out.println("返回: " + result);
}

@Around("servicePointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
Object result = pjp.proceed();
long end = System.currentTimeMillis();
System.out.println("耗时: " + (end - start) + "ms");
return result;
}
}

常用配置

属性配置

1
2
3
4
5
6
7
8
9
10
11
12
13
# application.yml
spring:
application:
name: myapp
profiles:
active: dev

server:
port: 8080

custom:
name: test
max: 100
1
2
3
4
5
@Value("${custom.name}")
private String name;

@Value("${custom.max}")
private Integer max;

多环境配置

  • application.yml(默认)
  • application-dev.yml
  • application-prod.yml

条件配置

1
2
3
@ConditionalOnProperty(name = "feature.enabled", havingValue = "true")
@ConditionalOnMissingBean(UserService.class)
@ConditionalOnClass(MongoTemplate.class)

Spring Boot自动配置

原理

@SpringBootApplication = @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan

@EnableAutoConfiguration 通过spring.factories和META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports加载配置。

自定义-starter

  1. 创建autoconfigure模块
  2. 定义配置类
  3. 创建spring.factories
  4. 创建starter模块依赖autoconfigure

总结

Spring核心概念包括IOC容器、依赖注入和AOP。理解这些概念是掌握Spring全家桶的基础。Spring Boot通过自动配置简化了开发,需要理解其原理以便更好地定制和调试。

MySQL外键绑定问题

问题

外键约束无法创建或绑定失败。

常见原因

  1. 类型不匹配
  2. 字符集不一致
  3. 索引缺失

解决方案

1
2
3
4
5
6
7
8
-- 确保关联字段类型一致
ALTER TABLE child MODIFY parent_id BIGINT;

-- 确保字符集一致
ALTER TABLE child CONVERT TO CHARACTER SET utf8mb4;

-- 手动创建索引
CREATE INDEX idx_parent_id ON child(parent_id);

总结

外键使用需注意细节。

MySQL锁知识点总结

概述

数据库锁是数据库管理系统中用于控制并发访问的机制。在MySQL中,锁机制直接影响事务的隔离级别和并发性能。本文总结了MySQL中常见的锁知识点。

锁的分类

1. 按粒度划分

  • 表级锁:锁定整个表,开销小,并发度低
  • 行级锁:锁定单行记录,开销大,并发度高
  • 页级锁:锁定数据页,介于表级和行级之间

2. 按模式划分

  • 共享锁(S锁):允许事务读取数据,多个事务可以同时持有
  • 排他锁(X锁):允许事务修改数据,其他事务不能同时持有

3. 意向锁

  • 意向共享锁(IS):事务意图在某些行上设置共享锁
  • 意向排他锁(IX):事务意图在某些行上设置排他锁

InnoDB锁机制

行锁

InnoDB存储引擎支持行级锁,通过索引实现:

  • Record Lock:锁定索引记录
  • Gap Lock:锁定索引间隙,防止幻读
  • Next-Key Lock:Record Lock + Gap Lock的组合

锁等待与死锁

  • 锁等待:当事务请求的锁被其他事务持有时,需要等待
  • 死锁:两个或多个事务相互等待对方释放锁

锁与事务隔离级别

隔离级别 锁机制
READ UNCOMMITTED 无锁
READ COMMITTED 记录锁
REPEATABLE READ Next-Key Lock
SERIALIZABLE 间隙锁+表锁

常见问题

1. 死锁的预防

  • 尽量使用索引,避免全表扫描
  • 保持事务简短,减少持有锁的时间
  • 统一访问顺序

2. 锁监控

1
2
3
4
5
6
7
8
-- 查看当前事务
SELECT * FROM information_schema.INNODB_TRX;

-- 查看当前锁
SELECT * FROM information_schema.INNODB_LOCKS;

-- 查看锁等待
SELECT * FROM information_schema.INNODB_LOCK_WAITS;

总结

理解MySQL锁机制对于优化数据库性能和避免并发问题至关重要。在实际开发中,需要根据业务场景选择合适的锁策略,合理设计索引,避免长时间持有锁导致的性能问题。

Intellij IDEA使用技巧

快捷键

快捷键 说明
Ctrl+Shift+N 全局搜索
Ctrl+Shift+F 全局查找
Ctrl+Alt+L 格式化代码
Ctrl+D 复制当前行
Ctrl+Y 删除当前行
Ctrl+Alt+T 包裹代码
Alt+Enter 快速修复

常用功能

  • Live Templates:代码模板
  • File and Code Templates:文件模板
  • Postfix Completion:后缀补全

调试技巧

  • 条件断点
  • 异常断点
  • Evaluate Expression

总结

熟练使用IDE能显著提升开发效率。

Spring @Async异步执行详解

基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class AsyncService {

@Async
public void asyncMethod() {
// 异步执行
}

@Async
public Future<String> asyncWithResult() {
return new AsyncResult<>("result");
}
}

启用异步

1
2
3
@EnableAsync
@Configuration
public class AsyncConfig {}

线程池

1
2
3
4
5
6
7
8
9
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-");
return executor;
}

总结

@Async简化了异步编程。

Java断言失效问题详解

什么是断言

断言是Java中的一种调试特性,用于验证程序假设。

1
assert x > 0 : "x must be positive";

断言默认关闭

1
2
3
4
5
# 启用断言
java -ea MyClass

# 禁用断言
java -da MyClass

常见问题

1. 线上环境断言不生效

1
2
// 代码中写了断言,但线上默认关闭
assert validate(input);

2. 断言会影响性能

1
2
3
4
// 复杂验证应使用条件判断
if (!validate(input)) {
throw new IllegalArgumentException();
}

最佳实践

  • 断言用于开发阶段验证
  • 重要校验不要依赖断言
  • 生产环境应使用if-else校验

总结

断言是调试工具,不是校验工具。重要逻辑应使用异常处理。

Mac使用技巧整理

常用快捷键

  • Command+C/V:复制/粘贴
  • Command+Z:撤销
  • Command+Space:Spotlight搜索
  • Command+Tab:切换应用
  • Command+Shift+3/4:截图

终端命令

1
2
3
open .           # 打开当前目录
pbcopy < file # 复制文件内容
pbpaste # 粘贴

软件推荐

  • Homebrew:包管理器
  • iTerm2:终端
  • Alfred:效率启动器

总结

Mac是开发者的好帮手。

Spring @Schedule定时任务详解

基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class ScheduledTask {

// 每5秒执行
@Scheduled(fixedRate = 5000)
public void task1() {}

// 每分钟执行
@Scheduled(cron = "0 * * * * *")
public void task2() {}

// 延迟3秒后执行
@Scheduled(fixedDelay = 3000)
public void task3() {}
}

常用配置

  • fixedRate:固定频率
  • fixedDelay:固定延迟
  • cron:Cron表达式
  • initialDelay:初始延迟

启用定时任务

1
2
3
@EnableScheduling
@SpringBootApplication
public class Application {}

总结

@Schedule是简单的定时任务方案。

0%