• [技术干货] SpringBoot登录认证全栈实现:Session、统一结果封装、MD5加密与拦截器
    本文简介目的:Spring生态为Java后端开发提供了强大支持,但将分散的技术点整合成完整解决方案往往令人困惑。本文将以登录接口为切入点,系统演示如何将IOC/DI、MyBatis数据持久化、MD5加密、Session/Cookie管理、JWT令牌和拦截器机制融合运用,打造企业级认证方案技术栈:前端:HTML + CSS + JavaScript + Jquery后端:SpringBoot + Mybatis + JWT搭建环境:数据库:MySQL8.4.0项目结构:maven前端框架:Jquery后端框架:SpringBootJDK:17编译器:IDEA目录结构:项目搭建及配置1.创建SpringBoot3.0.0+项目并添加依赖:Spring Web、MyBatis Framework、MySQL Driver、Lombok2.初始化数据库:create database spring_blog_login charset utf8mb4;      use spring_blog_login;create table user_info (id int primary key auto_increment,user_name varchar(128) unique ,                        password varchar(128) not null,delete_flag int default 0,                        create_time datetime default now(),update_time datetime default now());insert into user_info (user_name,password) values                                                ('张三','123456'),                                               ('李四','123456'),                                               ('王五','123456');运行本项目sql3.将application.properties修改为application.yml并添加如下配置:spring:  datasource:    url: jdbc:mysql://127.0.0.1:3306/spring_blog_login?characterEncoding=utf8&useSSL=false    username: root    password: 123456    driver-class-name: com.mysql.cj.jdbc.Drivermybatis:  configuration:    map-underscore-to-camel-case: true #自动驼峰转换server:  port: 8080 #不显式设置默认为8080运行本项目yml按住Ctrl + F5,如果程序能运行成功则说明搭建及配置都没问题(MySQL服务器必须要处于运行状态)1.登录认证全栈实现 ->基础版1.1 后端实现1.1.1 架构设计本次登录功能采用Controller、Service、Mapper三层架构:Controller层依赖于Service层来执行业务逻辑并获取处理结果,而Service层又依赖于Mapper层来进行数据持久化操作1.1.2 实体类实体类用于封装业务数据,需要与数据库表结构一一对应import lombok.Data;import java.util.Date;@Datapublic class UserInfo {    private Integer id;    private String userName;    private String password;    private Integer deleteFlag;    private Date createTime;    private Date updateTime;}运行本项目java运行1.1.3 Controller处理HTTP请求、参数校验、返回响应import org.example.springlogin.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.util.StringUtils;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("/user")public class UserController {    private final UserService userService;    @Autowired    public UserController(UserService userService) {        this.userService = userService;    }    @RequestMapping("/login")    public String login(String userName,String password) {        if (!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)) {            return "用户或密码为空";        }        return userService.getUserInfoByUserName(userName,password);    }}运行本项目java运行1.1.4 Service业务逻辑处理import org.example.springlogin.mapper.UserMapper;import org.example.springlogin.model.UserInfo;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Servicepublic class UserService {    private final UserMapper userMapper;    @Autowired    public UserService(UserMapper userMapper) {        this.userMapper = userMapper;    }    public String getUserInfoByUserName(String userName,String password) {        UserInfo userInfo = userMapper.getUserInfoByUserName(userName);        if (userInfo == null) {            return "用户不存在";        }        if (!password.equals(userInfo.getPassword())) {            return "密码错误";        }        return "登录成功";    }}运行本项目java运行1.1.5 Mapper数据持久化操作import org.apache.ibatis.annotations.Mapper;import org.apache.ibatis.annotations.Select;import org.example.springlogin.model.UserInfo;@Mapperpublic interface UserMapper {    @Select("select * from user_info where user_name = #{userName}")    UserInfo getUserInfoByUserName(String userName);}运行本项目java运行1.2 前端实现Gitee:项目前端代码,Gitee上的前端代码是最新提交的,如下效果图仅作参考效果演示:1.用户或密码为空2.用户不存在3.密码错误4.登录成功2.Cookie/SessionHTTP(超文本传输协议)设计为无状态协议,指服务器默认不保留客户端请求之间的任何状态信息。每个请求独立处理,服务器不会记忆之前的交互内容(如下图)优点:请求独立性:每次请求被视为新请求,服务器不依赖历史请求数据简单高效:无状态设计降低服务器资源消耗,简化实现逻辑缺点:身份识别困难:需通过额外机制(如Cookies、Session)跟踪用户状态重复传输数据:每次请求需携带完整信息,可能增加冗余(如认证信息)cookie:是存储在客户端(浏览器)的小型文本数据,由服务器通过HTTP响应头Set-Cookie发送给客户端,并在后续请求中自动携带session:是存储在服务器端的用户状态信息,通常通过一个唯一的Session ID标识,该ID可能通过Cookie或URL传递如上图片引用自我的博客:Java EE(13)——网络原理——应用层HTTP协议,服务器内部实际上专门开辟了一个session空间用于存储用户信息,每当新用户发送第一次请求时服务器会将用户信息存储在session中并生成一个session id通过Set-Cookie方法返回给客户端,即cookiesession结构如下:修改Controller类代码:import jakarta.servlet.http.HttpSession;import lombok.extern.slf4j.Slf4j;import org.example.springlogin.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.util.StringUtils;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;@RestController@RequestMapping("/user")@Slf4jpublic class UserController {    private final UserService userService;    @Autowired    public UserController(UserService userService) {        this.userService = userService;    }    @RequestMapping("/login")    public String login(String userName, String password, HttpSession session) {        log.info("接收到参数,userName:{},password:{}",userName,password);        if (!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)) {            return "用户或密码为空";        }        String result = userService.getUserInfoByUserName(userName, password);        if (result.equals("登录成功")){            HashMap<String,String> map = new HashMap<>();            map.put("userName",userName);            map.put("password",password);            //将map作为用户信息存储到session/会话中            session.setAttribute("cookie", map);            log.info("登录成功");        }        return result;    }运行本项目java运行修改前端代码:    function login() {      $.ajax({        url: '/user/login',        type: "post",        data:{          userName:$('#username').val(),          password:$('#password').val(),        },        success: function(result) {          alert(result);        },      })    }运行本项目javascript运行Fiddler抓包结果:前端/浏览器按住Ctrl + Shift + i打开控制台点击应用程序/application,打开Cookie:3.统一返回结果封装统一返回结果封装是后端开发中的重要设计模式,能够保持API响应格式的一致性,便于前端处理1.创建枚举类:统一管理接口或方法的返回状态码和描述信息,标准化业务逻辑中的成功或失败状态import lombok.Getter;@Getterpublic enum ResultStatus {    SUCCESS(200,"成功"),    FAIL(-1,"失败"),    ;    private final Integer code;    private final String message;    ResultStatus(Integer code, String message) {        this.code = code;        this.message = message;    }}运行本项目java运行2.创建Result< T >类:主要用于规范服务端返回给客户端的响应数据格式。通过固定结构(状态码、错误信息、数据)确保前后端交互的一致性import lombok.Data;@Data//通过泛型<T>设计,可以灵活封装任意类型的数据对象到data字段public class Result<T> {    //业务码    private ResultStatus code;    //错误信息    private String errorMessage;    //数据    private T data;    public static <T> Result<T> success(T data) {        Result<T> result = new Result<>();        result.setCode(ResultStatus.SUCCESS);        result.setErrorMessage(null);        result.setData(data);        return result;    }    public static <T> Result<T> fail(String errorMessage) {        Result<T> result = new Result<>();        result.setCode(ResultStatus.FAIL);        result.setErrorMessage(errorMessage);        result.setData(null);        return result;    }}运行本项目java运行3.修改Controller代码:@RestController@RequestMapping("/user")@Slf4jpublic class UserController {    private final UserService userService;    @Autowired    public UserController(UserService userService) {        this.userService = userService;    }    @RequestMapping("/login")    public Result<String> login(String userName, String password, HttpSession session) {        log.info("接收到参数,userName:{},password:{}",userName,password);        if (!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)) {            return Result.fail("用户或密码为空");        }        String result = userService.getUserInfoByUserName(userName, password);        if (!result.equals("登录成功")){            return Result.fail(result);        }        HashMap<String,String> map = new HashMap<>();        map.put("userName",userName);        map.put("password",password);        //将map作为用户信息存储到session/会话中        session.setAttribute("cookie", map);        log.info("登录成功");        return Result.success(result);    }}运行本项目java运行4.修改前端代码:    function login() {      $.ajax({        url: '/user/login',        type: "post",        data:{          userName:$('#username').val(),          password:$('#password').val(),        },        success: function(result) {          if (result.code === "SUCCESS") {            alert(result.data)          }else {            alert(result.error)          }        },      })    }运行本项目javascript运行4.图形验证码图形验证码(captcha)是一种区分用户是人类还是自动化程序的技术,主要通过视觉或交互任务实现。其核心意义体现在以下方面:防止自动化攻击:通过复杂图形或扭曲文字,阻止爬虫、暴力破解工具等自动化程序批量注册或登录,降低服务器压力提升安全性:在敏感操作(如支付、修改密码)前增加验证步骤,减少数据泄露或恶意操作风险Hutool提供了CaptchaUtil类用于快速生成验证码,支持图形验证码和GIF动态验证码。在pom.xml文件中添加图下配置:<dependency>    <groupId>cn.hutool</groupId>    <artifactId>hutool-all</artifactId>    <!-- 版本号应与springboot版本兼容 -->    <version>5.8.40</version></dependency>运行本项目xml1.创建CaptchaController类,用于生成验证码并返回给前端import cn.hutool.captcha.CaptchaUtil;import cn.hutool.captcha.LineCaptcha;import jakarta.servlet.http.HttpServletResponse;import jakarta.servlet.http.HttpSession;import lombok.extern.slf4j.Slf4j;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.io.IOException;@RestController@RequestMapping("/captcha")@Slf4jpublic class CaptchaController {    //设置过期时间    public final static long delay = 60_000L;    @RequestMapping("/get")    public void getCaptcha(HttpSession session, HttpServletResponse response) {        log.info("getCaptcha");        LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100);        //设置返回类型        response.setContentType("image/jpeg");        //禁止缓存        response.setHeader("Pragma", "No-cache");        try {            //通过响应输出生成的图形验证码            lineCaptcha.write(response.getOutputStream());            //保存code            session.setAttribute("CAPTCHA_SESSION_CODE", lineCaptcha.getCode());            //保存当前时间            session.setAttribute("CAPTCHA_SESSION_DATE", System.currentTimeMillis());            //关闭输出流            response.getOutputStream().close();        } catch (IOException e) {            throw new RuntimeException(e);        }    }}运行本项目java运行2.修改前端代码:最终版<!DOCTYPE html><html lang="zh-CN"><head>  <meta charset="UTF-8">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <title>微信登录</title>  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">  <link rel="stylesheet" href="css/login.css"></head><body>  <div class="login-container">    <div class="logo">      <i class="fab fa-weixin"></i>    </div>    <h2>微信登录</h2>    <form id="loginForm">      <div class="input-group">        <i class="fas fa-user"></i>        <label for="username"></label><input type="text" id="username" placeholder="请输入用户名" required>      </div>      <div class="input-group">        <i class="fas fa-lock"></i>        <label for="password"></label><input type="password" id="password" placeholder="请输入密码" required>      </div>      <div class="input-group">        <div class="captcha-container">          <label for="inputCaptcha"></label><input type="text" id="inputCaptcha" class="captcha-input" placeholder="输入验证码">          <img id="verificationCodeImg" src="/captcha/get" class="captcha-img" title="看不清?换一张" alt="验证码">        </div>      </div>      <div class="agreement">        <input type="checkbox" id="agreeCheck" checked>        <label for="agreeCheck">我已阅读并同意<a href="#">《服务条款》</a>和<a href="#">《隐私政策》</a></label>      </div>      <button type="submit" class="login-btn" onclick="login()">登录</button>    </form>    <div class="footer">      <p>版权所有 ©九转苍翎</p>    </div>  </div>  <!-- 引入jQuery依赖 -->  <script src="js/jquery.min.js"></script>  <script>    //刷新验证码    $("#verificationCodeImg").click(function(){      //new Date().getTime()).fadeIn()防止前端缓存      $(this).hide().attr('src', '/captcha/get?dt=' + new Date().getTime()).fadeIn();    });    //登录    function login() {      $.ajax({        url: '/user/login',        type: "post",        data:{          userName:$('#username').val(),          password:$('#password').val(),          captcha:$('#inputCaptcha').val(),        },        success: function(result) {          console.log(result);          if (result.code === "SUCCESS") {            alert(result.data)          }else {            alert(result.error)          }        },      })    }  </script></body></html>运行本项目html3.在UserController类新增captcha形参接收来自CaptchaController类的请求,并传递给UserServiceimport jakarta.servlet.http.HttpSession;import org.example.springlogin.controller.CaptchaController;import org.example.springlogin.mapper.UserMapper;import org.example.springlogin.model.UserInfo;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Servicepublic class UserService {    private final UserMapper userMapper;    @Autowired    public UserService(UserMapper userMapper) {        this.userMapper = userMapper;    }    public String getUserInfoByUserName(String userName, String password, String captcha, HttpSession session) {        UserInfo userInfo = userMapper.getUserInfoByUserName(userName);        if (userInfo == null) {            return "用户不存在";        }        if (!password.equals(userInfo.getPassword())) {            return "密码错误";        }        long saveTime = (long)session.getAttribute("CAPTCHA_SESSION_DATE");        if (System.currentTimeMillis() - saveTime > CaptchaController.delay) {            return "验证码超时";        }        if (!captcha.equalsIgnoreCase((String) session.getAttribute("CAPTCHA_SESSION_CODE"))) {            return "验证码错误";        }        return "登录成功";    }}运行本项目java运行实现效果:5.MD5加密MD5(Message-Digest Algorithm 5)是一种广泛使用的哈希函数,可将任意长度数据生成固定长度(128位,16字节)的哈希值,通常表示为32位十六进制字符串,常用于校验数据完整性或存储密码。但因其安全性不足,通常结合盐值(Salt)配合使用不可逆性:无法通过哈希值反推原始数据唯一性:理论上不同输入产生相同哈希值的概率极低(哈希碰撞)固定长度:无论输入数据大小,输出均为32位十六进制字符串1.创建SecurityUtil类用于生成和验证密文import org.springframework.util.DigestUtils;import org.springframework.util.StringUtils;import java.util.UUID;public class SecurityUtil {    //加密    public static String encrypt(String inputPassword){        //生成随机盐值        String salt = UUID.randomUUID().toString().replaceAll("-", "");        //(密码+盐值)进行加密        String finalPassword = DigestUtils.md5DigestAsHex((inputPassword + salt).getBytes());        return salt + finalPassword;    }    //验证    public static boolean verify(String inputPassword, String sqlPassword){        if (!StringUtils.hasLength(inputPassword)){            return false;        }        if (sqlPassword == null || sqlPassword.length() != 64){            return false;        }        //取出盐值        String salt = sqlPassword.substring(0,32);        //(输入密码 + 盐值)重新生成 加密密码        String finalPassword = DigestUtils.md5DigestAsHex((inputPassword + salt).getBytes());        //判断数据库中储存的密码与输入密码是否一致        return (salt + finalPassword).equals(sqlPassword);    }    public static void main(String[] args) {        System.out.println(SecurityUtil.encrypt("123456"));    }}运行本项目java运行2.将数据库中的密码替换为加密后的值3.修改验证密码的逻辑(UserService类)        if (!SecurityUtil.verify(password,userInfo.getPassword())) {            return "密码错误";        }运行本项目java运行6.拦截器Spring拦截器(Interceptor)是一种基于AOP的机制,用于在请求处理的不同阶段插入自定义逻辑。常用于权限校验、日志记录、参数预处理等场景1.创建拦截器类并实现HandlerInterceptor接口,该接口提供了三种方法:preHandle:在Controller方法执行前调用postHandle:Controller方法执行后、视图渲染前调用afterCompletion:请求完成、视图渲染完毕后调用import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Component;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;@Slf4j@Componentpublic class Interceptor implements HandlerInterceptor {    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {        //1.获取token        String cookie = request.getHeader("cookie");        if (cookie == null) {            response.setStatus(401);            return false;        }        log.info("接收到cookie:{}",cookie);        //2.校验token        return true;    }    @Override    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {        log.info("postHandle");    }    @Override    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {        log.info("afterCompletion");    }}运行本项目java运行2.注册拦截器import org.example.springlogin.intercepter.Interceptor;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import java.util.Arrays;import java.util.List;@Configurationpublic class Config implements WebMvcConfigurer {    private final Interceptor Interceptor;    @Autowired    public Config(Interceptor interceptor) {        Interceptor = interceptor;    }    //排除不需要拦截的路径    private static final List<String> excludes = Arrays.asList(            "/**/login.html",            "/user/login",            "/captcha/get"    );    @Override    public void addInterceptors(InterceptorRegistry registry) {        //注册拦截器        registry.addInterceptor(Interceptor)                //拦截所有路径                .addPathPatterns("/**")                .excludePathPatterns(excludes);    }}运行本项目java运行3.创建home.html文件,并且在登录成功后跳转到该页面(在login.html中添加location.href="/home.html")<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>home</title></head><body>    <h1>Hello World</h1></body></html>运行本项目html实现效果:成功登陆时未登录直接访问home.html页面时————————————————原文链接:https://blog.csdn.net/2401_89167985/article/details/153261457
  • [技术干货] 【Java 进阶】重生之我要吃透 Spring 事务管理
    前言在现代企业级Java应用开发中,事务管理是确保数据一致性和完整性的核心机制。Spring框架作为Java生态系统中最重要的框架之一,提供了强大而灵活的事务管理功能。本文将从基础概念出发,深入探讨Spring事务管理的各个方面,通过丰富的代码示例和实践案例,帮助开发者全面掌握Spring事务管理的精髓。无论你是刚接触Spring事务的新手,还是希望深化理解的资深开发者,本文都将为你提供有价值的见解和实用的技巧。我们将先概览Spring事务的整体架构,然后深入各个具体模块,最后进行知识总结和扩展思考。第一章:Spring事务基础概念与核心原理1.1 事务的基本概念事务(Transaction)是数据库操作的基本单位,它是一个不可分割的工作逻辑单元。事务必须满足ACID特性:原子性(Atomicity):事务中的所有操作要么全部成功,要么全部失败回滚一致性(Consistency):事务执行前后,数据库的完整性约束没有被破坏隔离性(Isolation):并发执行的事务之间不能互相干扰持久性(Durability):事务一旦提交,其结果就是永久性的1.2 Spring事务管理架构Spring事务管理基于以下核心组件:1.2.1 PlatformTransactionManager接口public interface PlatformTransactionManager {    TransactionStatus getTransaction(TransactionDefinition definition)         throws TransactionException;        void commit(TransactionStatus status) throws TransactionException;        void rollback(TransactionStatus status) throws TransactionException;}运行本项目java运行这是Spring事务管理的核心接口,定义了事务的基本操作。不同的数据访问技术有不同的实现:DataSourceTransactionManager:用于JDBC和MyBatisJpaTransactionManager:用于JPAHibernateTransactionManager:用于Hibernate1.2.2 TransactionDefinition接口public interface TransactionDefinition {    int PROPAGATION_REQUIRED = 0;    int PROPAGATION_SUPPORTS = 1;    int PROPAGATION_MANDATORY = 2;    // ... 其他传播行为常量        int getPropagationBehavior();    int getIsolationLevel();    int getTimeout();    boolean isReadOnly();    String getName();}运行本项目java运行1.3 Spring事务管理的实现原理Spring事务管理基于AOP(面向切面编程)实现,通过代理模式在方法调用前后添加事务逻辑:@Componentpublic class UserService {        @Autowired    private UserRepository userRepository;        @Transactional    public void createUser(User user) {        // Spring会在此方法执行前开启事务        userRepository.save(user);        // 方法正常结束时提交事务,异常时回滚    }}运行本项目java运行当Spring容器创建UserService的代理对象时,会织入事务管理逻辑:// 简化的代理逻辑示意public class UserServiceProxy extends UserService {        private PlatformTransactionManager transactionManager;        @Override    public void createUser(User user) {        TransactionStatus status = null;        try {            // 开启事务            status = transactionManager.getTransaction(new DefaultTransactionDefinition());                        // 调用实际的业务方法            super.createUser(user);                        // 提交事务            transactionManager.commit(status);        } catch (Exception e) {            // 回滚事务            if (status != null) {                transactionManager.rollback(status);            }            throw e;        }    }}运行本项目java运行第二章:Spring事务管理器详解2.1 事务管理器的选择与配置2.1.1 DataSourceTransactionManager配置对于使用JDBC或MyBatis的应用,通常选择DataSourceTransactionManager:@Configuration@EnableTransactionManagementpublic class TransactionConfig {        @Bean    public DataSource dataSource() {        HikariDataSource dataSource = new HikariDataSource();        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/testdb");        dataSource.setUsername("root");        dataSource.setPassword("password");        return dataSource;    }        @Bean    public PlatformTransactionManager transactionManager(DataSource dataSource) {        return new DataSourceTransactionManager(dataSource);    }        @Bean    public JdbcTemplate jdbcTemplate(DataSource dataSource) {        return new JdbcTemplate(dataSource);    }}运行本项目java运行2.1.2 JpaTransactionManager配置对于JPA应用,使用JpaTransactionManager:@Configuration@EnableTransactionManagement@EnableJpaRepositories(basePackages = "com.example.repository")public class JpaConfig {        @Bean    public LocalContainerEntityManagerFactoryBean entityManagerFactory(            DataSource dataSource) {        LocalContainerEntityManagerFactoryBean em =             new LocalContainerEntityManagerFactoryBean();        em.setDataSource(dataSource);        em.setPackagesToScan("com.example.entity");                HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();        em.setJpaVendorAdapter(vendorAdapter);                Properties properties = new Properties();        properties.setProperty("hibernate.hbm2ddl.auto", "update");        properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect");        em.setJpaProperties(properties);                return em;    }        @Bean    public PlatformTransactionManager transactionManager(            EntityManagerFactory entityManagerFactory) {        JpaTransactionManager transactionManager = new JpaTransactionManager();        transactionManager.setEntityManagerFactory(entityManagerFactory);        return transactionManager;    }}运行本项目java运行2.2 多数据源事务管理在复杂的企业应用中,经常需要处理多个数据源的事务:@Configuration@EnableTransactionManagementpublic class MultiDataSourceConfig {        @Bean    @Primary    public DataSource primaryDataSource() {        // 主数据源配置        return DataSourceBuilder.create()            .url("jdbc:mysql://localhost:3306/primary_db")            .username("root")            .password("password")            .build();    }        @Bean    public DataSource secondaryDataSource() {        // 从数据源配置        return DataSourceBuilder.create()            .url("jdbc:mysql://localhost:3306/secondary_db")            .username("root")            .password("password")            .build();    }        @Bean    @Primary    public PlatformTransactionManager primaryTransactionManager(            @Qualifier("primaryDataSource") DataSource dataSource) {        return new DataSourceTransactionManager(dataSource);    }        @Bean    public PlatformTransactionManager secondaryTransactionManager(            @Qualifier("secondaryDataSource") DataSource dataSource) {        return new DataSourceTransactionManager(dataSource);    }}运行本项目java运行使用时可以通过@Transactional注解指定事务管理器:@Servicepublic class MultiDataSourceService {        @Transactional("primaryTransactionManager")    public void operateOnPrimaryDB() {        // 操作主数据库    }        @Transactional("secondaryTransactionManager")    public void operateOnSecondaryDB() {        // 操作从数据库    }}运行本项目java运行2.3 分布式事务管理对于跨多个资源的分布式事务,Spring提供了JTA支持:@Configuration@EnableTransactionManagementpublic class JtaConfig {        @Bean    public JtaTransactionManager transactionManager() {        JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();        jtaTransactionManager.setUserTransaction(userTransaction());        jtaTransactionManager.setTransactionManager(atomikosTransactionManager());        return jtaTransactionManager;    }        @Bean    public UserTransaction userTransaction() throws SystemException {        UserTransactionImp userTransactionImp = new UserTransactionImp();        userTransactionImp.setTransactionTimeout(300);        return userTransactionImp;    }        @Bean    public TransactionManager atomikosTransactionManager() {        UserTransactionManager userTransactionManager = new UserTransactionManager();        userTransactionManager.setForceShutdown(false);        return userTransactionManager;    }}运行本项目java运行第三章:声明式事务配置与使用3.1 @Transactional注解详解@Transactional是Spring声明式事务的核心注解,提供了丰富的配置选项:@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic @interface Transactional {    String value() default "";    String transactionManager() default "";    Propagation propagation() default Propagation.REQUIRED;    Isolation isolation() default Isolation.DEFAULT;    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;    boolean readOnly() default false;    Class<? extends Throwable>[] rollbackFor() default {};    String[] rollbackForClassName() default {};    Class<? extends Throwable>[] noRollbackFor() default {};    String[] noRollbackForClassName() default {};}运行本项目java运行3.2 注解使用最佳实践3.2.1 类级别与方法级别注解@Service@Transactional(readOnly = true) // 类级别默认只读事务public class UserService {        @Autowired    private UserRepository userRepository;        // 继承类级别的只读事务    public List<User> findAllUsers() {        return userRepository.findAll();    }        // 方法级别覆盖类级别配置    @Transactional(readOnly = false, rollbackFor = Exception.class)    public User createUser(User user) {        validateUser(user);        return userRepository.save(user);    }        @Transactional(propagation = Propagation.REQUIRES_NEW)    public void updateUserStatus(Long userId, UserStatus status) {        User user = userRepository.findById(userId)            .orElseThrow(() -> new UserNotFoundException("User not found"));        user.setStatus(status);        userRepository.save(user);    }        private void validateUser(User user) {        if (user.getEmail() == null || user.getEmail().isEmpty()) {            throw new IllegalArgumentException("Email cannot be empty");        }    }}运行本项目java运行3.2.2 异常处理与回滚配置@Servicepublic class OrderService {        @Autowired    private OrderRepository orderRepository;        @Autowired    private InventoryService inventoryService;        @Autowired    private PaymentService paymentService;        // 默认只对RuntimeException和Error回滚    @Transactional    public Order createOrder(OrderRequest request) {        Order order = new Order(request);        orderRepository.save(order);                // 如果库存不足,抛出RuntimeException,事务会回滚        inventoryService.reserveItems(request.getItems());                return order;    }        // 指定对所有异常都回滚    @Transactional(rollbackFor = Exception.class)    public void processPayment(Long orderId, PaymentInfo paymentInfo)             throws PaymentException {        Order order = orderRepository.findById(orderId)            .orElseThrow(() -> new OrderNotFoundException("Order not found"));                try {            paymentService.processPayment(paymentInfo);            order.setStatus(OrderStatus.PAID);            orderRepository.save(order);        } catch (PaymentException e) {            // PaymentException是检查异常,但配置了rollbackFor = Exception.class            // 所以事务会回滚            throw e;        }    }        // 指定某些异常不回滚    @Transactional(noRollbackFor = {BusinessException.class})    public void updateOrderWithBusinessLogic(Long orderId) {        Order order = orderRepository.findById(orderId)            .orElseThrow(() -> new OrderNotFoundException("Order not found"));                try {            // 执行业务逻辑            performBusinessLogic(order);            orderRepository.save(order);        } catch (BusinessException e) {            // BusinessException不会导致事务回滚            // 但数据库操作仍然会提交            log.warn("Business logic failed, but transaction will commit", e);        }    }}运行本项目java运行3.3 XML配置方式虽然注解方式更加流行,但XML配置在某些场景下仍然有用:<!-- applicationContext.xml --><beans xmlns="http://www.springframework.org/schema/beans"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       xmlns:tx="http://www.springframework.org/schema/tx"       xmlns:aop="http://www.springframework.org/schema/aop"       xsi:schemaLocation="           http://www.springframework.org/schema/beans           http://www.springframework.org/schema/beans/spring-beans.xsd           http://www.springframework.org/schema/tx           http://www.springframework.org/schema/tx/spring-tx.xsd           http://www.springframework.org/schema/aop           http://www.springframework.org/schema/aop/spring-aop.xsd">    <!-- 数据源配置 -->    <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/testdb"/>        <property name="username" value="root"/>        <property name="password" value="password"/>    </bean>    <!-- 事务管理器 -->    <bean id="transactionManager"           class="org.springframework.jdbc.datasource.DataSourceTransactionManager">        <property name="dataSource" ref="dataSource"/>    </bean>    <!-- 事务通知 -->    <tx:advice id="txAdvice" transaction-manager="transactionManager">        <tx:attributes>            <tx:method name="find*" read-only="true"/>            <tx:method name="get*" read-only="true"/>            <tx:method name="*" rollback-for="Exception"/>        </tx:attributes>    </tx:advice>    <!-- AOP配置 -->    <aop:config>        <aop:pointcut id="serviceOperation"                       expression="execution(* com.example.service.*.*(..))"/>        <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceOperation"/>    </aop:config></beans>运行本项目xml第四章:编程式事务管理4.1 TransactionTemplate使用TransactionTemplate提供了编程式事务管理的模板方法:@Servicepublic class ProgrammaticTransactionService {        @Autowired    private TransactionTemplate transactionTemplate;        @Autowired    private UserRepository userRepository;        @Autowired    private OrderRepository orderRepository;        public User createUserWithOrder(User user, Order order) {        return transactionTemplate.execute(status -> {            try {                // 保存用户                User savedUser = userRepository.save(user);                                // 设置订单的用户ID                order.setUserId(savedUser.getId());                                // 保存订单                orderRepository.save(order);                                return savedUser;            } catch (Exception e) {                // 手动标记回滚                status.setRollbackOnly();                throw new RuntimeException("Failed to create user with order", e);            }        });    }        public void batchUpdateUsers(List<User> users) {        transactionTemplate.execute(status -> {            for (User user : users) {                try {                    userRepository.save(user);                } catch (Exception e) {                    log.error("Failed to update user: {}", user.getId(), e);                    // 可以选择继续处理其他用户,或者回滚整个事务                    // status.setRollbackOnly();                }            }            return null;        });    }}运行本项目java运行4.2 PlatformTransactionManager直接使用对于更细粒度的控制,可以直接使用PlatformTransactionManager:@Servicepublic class LowLevelTransactionService {        @Autowired    private PlatformTransactionManager transactionManager;        @Autowired    private UserRepository userRepository;        public void complexBusinessOperation() {        // 定义事务属性        DefaultTransactionDefinition def = new DefaultTransactionDefinition();        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);        def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);        def.setTimeout(30);                TransactionStatus status = transactionManager.getTransaction(def);                try {            // 第一阶段操作            performPhaseOne();                        // 检查点 - 可以根据业务逻辑决定是否继续            if (!shouldContinue()) {                transactionManager.rollback(status);                return;            }                        // 第二阶段操作            performPhaseTwo();                        // 提交事务            transactionManager.commit(status);                    } catch (Exception e) {            // 回滚事务            transactionManager.rollback(status);            throw new RuntimeException("Business operation failed", e);        }    }        private void performPhaseOne() {        // 第一阶段业务逻辑    }        private void performPhaseTwo() {        // 第二阶段业务逻辑    }        private boolean shouldContinue() {        // 业务判断逻辑        return true;    }}运行本项目java运行4.3 编程式事务的嵌套使用@Servicepublic class NestedTransactionService {        @Autowired    private PlatformTransactionManager transactionManager;        public void parentOperation() {        DefaultTransactionDefinition parentDef = new DefaultTransactionDefinition();        parentDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);                TransactionStatus parentStatus = transactionManager.getTransaction(parentDef);                try {            // 父事务操作            performParentOperation();                        // 调用子事务            childOperation();                        transactionManager.commit(parentStatus);        } catch (Exception e) {            transactionManager.rollback(parentStatus);            throw e;        }    }        private void childOperation() {        DefaultTransactionDefinition childDef = new DefaultTransactionDefinition();        childDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);                TransactionStatus childStatus = transactionManager.getTransaction(childDef);                try {            // 子事务操作(独立的事务)            performChildOperation();                        transactionManager.commit(childStatus);        } catch (Exception e) {            transactionManager.rollback(childStatus);            // 子事务失败不影响父事务(因为使用了REQUIRES_NEW)            log.error("Child operation failed", e);        }    }}运行本项目java运行第五章:事务传播行为和隔离级别深度解析5.1 事务传播行为详解Spring定义了7种事务传播行为,每种都有其特定的使用场景:5.1.1 PROPAGATION_REQUIRED(默认)@Servicepublic class PropagationRequiredService {        @Autowired    private UserService userService;        @Transactional(propagation = Propagation.REQUIRED)    public void outerMethod() {        // 开启新事务T1        performOperation1();                // 调用内部方法,加入事务T1        userService.innerMethod();                performOperation2();        // 如果任何操作失败,整个事务T1回滚    }}@Servicepublic class UserService {        @Transactional(propagation = Propagation.REQUIRED)    public void innerMethod() {        // 加入外部事务T1,不会创建新事务        performUserOperation();    }}运行本项目java运行5.1.2 PROPAGATION_REQUIRES_NEW@Servicepublic class PropagationRequiresNewService {        @Autowired    private AuditService auditService;        @Transactional    public void businessOperation() {        try {            // 主业务逻辑在事务T1中            performMainBusiness();                        // 审计日志使用独立事务T2            auditService.logOperation("Business operation completed");                    } catch (Exception e) {            // 即使主业务失败,审计日志也会保存(因为是独立事务)            auditService.logError("Business operation failed: " + e.getMessage());            throw e;        }    }}@Servicepublic class AuditService {        @Transactional(propagation = Propagation.REQUIRES_NEW)    public void logOperation(String message) {        // 创建新事务T2,独立于调用者的事务        AuditLog log = new AuditLog(message, new Date());        auditRepository.save(log);        // T2独立提交,不受T1影响    }        @Transactional(propagation = Propagation.REQUIRES_NEW)    public void logError(String errorMessage) {        // 错误日志也使用独立事务,确保一定会保存        ErrorLog errorLog = new ErrorLog(errorMessage, new Date());        errorRepository.save(errorLog);    }}运行本项目java运行5.1.3 PROPAGATION_NESTED@Servicepublic class PropagationNestedService {        @Autowired    private OrderService orderService;        @Transactional    public void processOrderBatch(List<OrderRequest> orders) {        for (OrderRequest orderRequest : orders) {            try {                // 每个订单处理使用嵌套事务                orderService.processOrder(orderRequest);            } catch (Exception e) {                // 单个订单失败不影响其他订单                log.error("Failed to process order: {}", orderRequest.getId(), e);            }        }    }}@Servicepublic class OrderService {        @Transactional(propagation = Propagation.NESTED)    public void processOrder(OrderRequest request) {        // 创建嵌套事务(保存点)        Order order = new Order(request);        orderRepository.save(order);                // 如果这里抛出异常,只回滚到保存点        // 外部事务可以继续执行        validateAndProcessPayment(order);    }}运行本项目java运行5.1.4 其他传播行为示例@Servicepublic class OtherPropagationService {        // PROPAGATION_SUPPORTS:支持当前事务,如果没有事务则以非事务方式执行    @Transactional(propagation = Propagation.SUPPORTS)    public List<User> findUsers() {        // 如果在事务中调用,加入事务        // 如果不在事务中调用,以非事务方式执行        return userRepository.findAll();    }        // PROPAGATION_NOT_SUPPORTED:以非事务方式执行,如果当前存在事务则挂起    @Transactional(propagation = Propagation.NOT_SUPPORTED)    public void performNonTransactionalOperation() {        // 总是以非事务方式执行        // 如果当前有事务,会被挂起        performLongRunningOperation();    }        // PROPAGATION_MANDATORY:必须在事务中执行,否则抛出异常    @Transactional(propagation = Propagation.MANDATORY)    public void mandatoryTransactionMethod() {        // 如果没有活动事务,抛出IllegalTransactionStateException        performCriticalOperation();    }        // PROPAGATION_NEVER:不能在事务中执行,否则抛出异常    @Transactional(propagation = Propagation.NEVER)    public void neverInTransactionMethod() {        // 如果当前有活动事务,抛出IllegalTransactionStateException        performIndependentOperation();    }}运行本项目java运行5.2 事务隔离级别详解5.2.1 隔离级别对比@Servicepublic class IsolationLevelService {        // READ_UNCOMMITTED:最低隔离级别,可能出现脏读    @Transactional(isolation = Isolation.READ_UNCOMMITTED)    public List<User> readUncommittedExample() {        // 可能读取到其他事务未提交的数据        return userRepository.findAll();    }        // READ_COMMITTED:防止脏读,但可能出现不可重复读    @Transactional(isolation = Isolation.READ_COMMITTED)    public User readCommittedExample(Long userId) {        User user1 = userRepository.findById(userId).orElse(null);                // 在这期间,其他事务可能修改了用户数据        performSomeOperation();                User user2 = userRepository.findById(userId).orElse(null);        // user1和user2可能不同(不可重复读)                return user2;    }        // REPEATABLE_READ:防止脏读和不可重复读,但可能出现幻读    @Transactional(isolation = Isolation.REPEATABLE_READ)    public List<User> repeatableReadExample() {        List<User> users1 = userRepository.findByStatus(UserStatus.ACTIVE);                performSomeOperation();                List<User> users2 = userRepository.findByStatus(UserStatus.ACTIVE);        // users1和users2中的现有记录相同,但users2可能包含新插入的记录(幻读)                return users2;    }        // SERIALIZABLE:最高隔离级别,防止所有并发问题    @Transactional(isolation = Isolation.SERIALIZABLE)    public void serializableExample() {        // 完全串行化执行,性能最低但数据一致性最高        List<User> users = userRepository.findAll();                for (User user : users) {            user.setLastAccessTime(new Date());            userRepository.save(user);        }    }}运行本项目java运行5.2.2 隔离级别实际应用场景@Servicepublic class BankingService {        @Autowired    private AccountRepository accountRepository;        // 转账操作需要高隔离级别确保数据一致性    @Transactional(isolation = Isolation.SERIALIZABLE)    public void transfer(Long fromAccountId, Long toAccountId, BigDecimal amount) {        Account fromAccount = accountRepository.findById(fromAccountId)            .orElseThrow(() -> new AccountNotFoundException("From account not found"));                Account toAccount = accountRepository.findById(toAccountId)            .orElseThrow(() -> new AccountNotFoundException("To account not found"));                if (fromAccount.getBalance().compareTo(amount) < 0) {            throw new InsufficientFundsException("Insufficient funds");        }                fromAccount.setBalance(fromAccount.getBalance().subtract(amount));        toAccount.setBalance(toAccount.getBalance().add(amount));                accountRepository.save(fromAccount);        accountRepository.save(toAccount);    }        // 查询余额可以使用较低的隔离级别    @Transactional(isolation = Isolation.READ_COMMITTED, readOnly = true)    public BigDecimal getBalance(Long accountId) {        Account account = accountRepository.findById(accountId)            .orElseThrow(() -> new AccountNotFoundException("Account not found"));        return account.getBalance();    }        // 生成对账单可以使用REPEATABLE_READ确保数据一致性    @Transactional(isolation = Isolation.REPEATABLE_READ, readOnly = true)    public AccountStatement generateStatement(Long accountId, Date startDate, Date endDate) {        Account account = accountRepository.findById(accountId)            .orElseThrow(() -> new AccountNotFoundException("Account not found"));                List<Transaction> transactions = transactionRepository            .findByAccountIdAndDateBetween(accountId, startDate, endDate);                return new AccountStatement(account, transactions, startDate, endDate);    }}运行本项目java运行第六章:Spring事务最佳实践和常见问题6.1 事务使用最佳实践6.1.1 事务边界设计//  错误示例:事务边界过大@Servicepublic class BadTransactionService {        @Transactional    public void processLargeDataSet(List<Data> dataList) {        for (Data data : dataList) {            // 长时间运行的操作            processComplexData(data);                        // 外部服务调用            externalService.sendNotification(data);                        // 文件操作            fileService.writeToFile(data);        }        // 事务持续时间过长,容易导致锁竞争和超时    }}//  正确示例:合理的事务边界@Servicepublic class GoodTransactionService {        public void processLargeDataSet(List<Data> dataList) {        for (Data data : dataList) {            try {                // 每个数据项使用独立事务                processSingleData(data);            } catch (Exception e) {                log.error("Failed to process data: {}", data.getId(), e);                // 单个失败不影响其他数据处理            }        }    }        @Transactional    public void processSingleData(Data data) {        // 只包含数据库操作的事务        dataRepository.save(processData(data));                // 非事务操作放在事务外        CompletableFuture.runAsync(() -> {            externalService.sendNotification(data);            fileService.writeToFile(data);        });    }}运行本项目java运行6.1.2 只读事务优化@Servicepublic class OptimizedReadService {        // 明确标记只读事务    @Transactional(readOnly = true)    public List<User> findActiveUsers() {        return userRepository.findByStatus(UserStatus.ACTIVE);    }        // 复杂查询使用只读事务    @Transactional(readOnly = true)    public UserStatistics generateUserStatistics() {        long totalUsers = userRepository.count();        long activeUsers = userRepository.countByStatus(UserStatus.ACTIVE);        long inactiveUsers = userRepository.countByStatus(UserStatus.INACTIVE);                return new UserStatistics(totalUsers, activeUsers, inactiveUsers);    }        // 分页查询优化    @Transactional(readOnly = true)    public Page<User> findUsersWithPagination(Pageable pageable) {        return userRepository.findAll(pageable);    }}运行本项目java运行6.1.3 异常处理策略@Servicepublic class ExceptionHandlingService {        @Transactional(rollbackFor = Exception.class)    public void robustBusinessOperation(BusinessRequest request) {        try {            // 主要业务逻辑            performMainOperation(request);                    } catch (ValidationException e) {            // 验证异常,记录日志但不回滚            log.warn("Validation failed: {}", e.getMessage());            throw new BusinessException("Invalid request", e);                    } catch (ExternalServiceException e) {            // 外部服务异常,可能需要重试            log.error("External service failed: {}", e.getMessage());                        // 标记为需要重试            markForRetry(request);            throw e;                    } catch (Exception e) {            // 其他异常,记录详细信息            log.error("Unexpected error in business operation", e);            throw new SystemException("System error occurred", e);        }    }        // 使用自定义异常控制回滚行为    @Transactional(rollbackFor = {DataIntegrityException.class},                    noRollbackFor = {BusinessWarningException.class})    public void selectiveRollbackOperation() {        try {            performDataOperation();        } catch (BusinessWarningException e) {            // 业务警告不回滚事务,但记录警告            log.warn("Business warning: {}", e.getMessage());        }    }}运行本项目java运行6.2 常见问题与解决方案6.2.1 事务失效问题//  问题:内部方法调用导致事务失效@Servicepublic class TransactionFailureService {        public void publicMethod() {        // 直接调用内部方法,@Transactional不会生效        this.internalTransactionalMethod();    }        @Transactional    private void internalTransactionalMethod() {        // 事务不会生效,因为是内部调用        performDatabaseOperation();    }}// 解决方案1:使用自注入@Servicepublic class SelfInjectionService {        @Autowired    private SelfInjectionService self;        public void publicMethod() {        // 通过代理对象调用,事务生效        self.internalTransactionalMethod();    }        @Transactional    public void internalTransactionalMethod() {        performDatabaseOperation();    }}// ✅ 解决方案2:拆分到不同的Service@Servicepublic class CallerService {        @Autowired    private TransactionalService transactionalService;        public void publicMethod() {        transactionalService.transactionalMethod();    }}@Servicepublic class TransactionalService {        @Transactional    public void transactionalMethod() {        performDatabaseOperation();    }}运行本项目java运行6.2.2 事务超时处理@Servicepublic class TimeoutHandlingService {        // 设置合理的超时时间    @Transactional(timeout = 30) // 30秒超时    public void normalOperation() {        performQuickOperation();    }        // 长时间运行的操作需要更长的超时时间    @Transactional(timeout = 300) // 5分钟超时    public void longRunningOperation() {        performBatchOperation();    }        // 对于可能很长的操作,考虑分批处理    public void processLargeDataset(List<Data> dataList) {        int batchSize = 100;        for (int i = 0; i < dataList.size(); i += batchSize) {            List<Data> batch = dataList.subList(i,                 Math.min(i + batchSize, dataList.size()));                        processBatch(batch);        }    }        @Transactional(timeout = 60)    private void processBatch(List<Data> batch) {        for (Data data : batch) {            dataRepository.save(data);        }    }}运行本项目java运行6.2.3 死锁预防@Servicepublic class DeadlockPreventionService {        // 按固定顺序获取锁,避免死锁    @Transactional    public void transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) {        // 确保按ID顺序获取锁        Long firstId = Math.min(fromAccountId, toAccountId);        Long secondId = Math.max(fromAccountId, toAccountId);                Account firstAccount = accountRepository.findByIdForUpdate(firstId);        Account secondAccount = accountRepository.findByIdForUpdate(secondId);                Account fromAccount = fromAccountId.equals(firstId) ? firstAccount : secondAccount;        Account toAccount = toAccountId.equals(firstId) ? firstAccount : secondAccount;                // 执行转账逻辑        performTransfer(fromAccount, toAccount, amount);    }        // 使用乐观锁避免死锁    @Transactional    public void updateAccountWithOptimisticLock(Long accountId, BigDecimal amount) {        int maxRetries = 3;        int retryCount = 0;                while (retryCount < maxRetries) {            try {                Account account = accountRepository.findById(accountId)                    .orElseThrow(() -> new AccountNotFoundException("Account not found"));                                account.setBalance(account.getBalance().add(amount));                accountRepository.save(account);                                return; // 成功,退出重试循环                            } catch (OptimisticLockingFailureException e) {                retryCount++;                if (retryCount >= maxRetries) {                    throw new ConcurrencyException("Failed to update account after " + maxRetries + " retries");                }                                // 短暂等待后重试                try {                    Thread.sleep(100 * retryCount);                } catch (InterruptedException ie) {                    Thread.currentThread().interrupt();                    throw new RuntimeException("Thread interrupted", ie);                }            }        }    }}运行本项目java运行6.3 性能优化技巧6.3.1 批量操作优化@Servicepublic class BatchOptimizationService {        //  低效的逐条处理    @Transactional    public void inefficientBatchInsert(List<User> users) {        for (User user : users) {            userRepository.save(user); // 每次都执行SQL        }    }        // 高效的批量处理    @Transactional    public void efficientBatchInsert(List<User> users) {        int batchSize = 50;        for (int i = 0; i < users.size(); i += batchSize) {            List<User> batch = users.subList(i, Math.min(i + batchSize, users.size()));            userRepository.saveAll(batch);                        // 每批次后清理持久化上下文            if (i % batchSize == 0) {                entityManager.flush();                entityManager.clear();            }        }    }        // 使用JDBC批量操作    @Transactional    public void jdbcBatchInsert(List<User> users) {        String sql = "INSERT INTO users (name, email, status) VALUES (?, ?, ?)";                jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {            @Override            public void setValues(PreparedStatement ps, int i) throws SQLException {                User user = users.get(i);                ps.setString(1, user.getName());                ps.setString(2, user.getEmail());                ps.setString(3, user.getStatus().name());            }                        @Override            public int getBatchSize() {                return users.size();            }        });    }}运行本项目java运行6.3.2 连接池配置优化@Configurationpublic class DataSourceOptimizationConfig {        @Bean    public DataSource optimizedDataSource() {        HikariConfig config = new HikariConfig();                // 基本连接配置        config.setJdbcUrl("jdbc:mysql://localhost:3306/testdb");        config.setUsername("root");        config.setPassword("password");                // 连接池优化配置        config.setMaximumPoolSize(20); // 最大连接数        config.setMinimumIdle(5); // 最小空闲连接数        config.setConnectionTimeout(30000); // 连接超时时间        config.setIdleTimeout(600000); // 空闲连接超时时间        config.setMaxLifetime(1800000); // 连接最大生存时间                // 性能优化配置        config.setLeakDetectionThreshold(60000); // 连接泄漏检测        config.addDataSourceProperty("cachePrepStmts", "true");        config.addDataSourceProperty("prepStmtCacheSize", "250");        config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");                return new HikariDataSource(config);    }}运行本项目java运行第七章:总结与展望7.1 核心知识点回顾通过本文的深入探讨,我们全面了解了Spring事务管理的各个方面:7.1.1 基础概念掌握ACID特性:原子性、一致性、隔离性、持久性是事务的基本保证Spring事务架构:PlatformTransactionManager、TransactionDefinition、TransactionStatus三大核心接口AOP实现原理:基于代理模式的声明式事务管理机制7.1.2 实践技能提升声明式事务:@Transactional注解的灵活使用和最佳实践编程式事务:TransactionTemplate和PlatformTransactionManager的直接使用传播行为:7种传播行为的适用场景和实际应用隔离级别:4种隔离级别的性能与一致性权衡7.1.3 问题解决能力常见陷阱:事务失效、死锁、超时等问题的识别和解决性能优化:批量操作、连接池配置、事务边界设计最佳实践:异常处理、只读事务、多数据源管理7.2 进阶学习路径7.2.1 深入源码研究// 建议研究的核心类// 1. AbstractPlatformTransactionManager - 事务管理器抽象实现// 2. TransactionInterceptor - 事务拦截器// 3. TransactionAspectSupport - 事务切面支持// 4. DefaultTransactionStatus - 事务状态实现// 示例:自定义事务管理器public class CustomTransactionManager extends AbstractPlatformTransactionManager {        @Override    protected Object doGetTransaction() throws TransactionException {        // 获取事务对象的实现        return new CustomTransactionObject();    }        @Override    protected void doBegin(Object transaction, TransactionDefinition definition)             throws TransactionException {        // 开始事务的实现    }        @Override    protected void doCommit(DefaultTransactionStatus status)             throws TransactionException {        // 提交事务的实现    }        @Override    protected void doRollback(DefaultTransactionStatus status)             throws TransactionException {        // 回滚事务的实现    }}运行本项目java运行7.2.2 分布式事务探索// Seata分布式事务示例@GlobalTransactionalpublic class DistributedTransactionService {        @Autowired    private OrderService orderService;        @Autowired    private PaymentService paymentService;        @Autowired    private InventoryService inventoryService;        public void createOrder(OrderRequest request) {        // 创建订单        Order order = orderService.createOrder(request);                // 扣减库存        inventoryService.deductInventory(request.getItems());                // 处理支付        paymentService.processPayment(order.getId(), request.getPaymentInfo());                // 如果任何服务失败,全局事务会回滚    }}运行本项目java运行7.2.3 响应式事务管理// Spring WebFlux响应式事务@Servicepublic class ReactiveTransactionService {        @Autowired    private ReactiveTransactionManager transactionManager;        public Mono<User> createUserReactive(User user) {        return transactionManager.execute(status -> {            return userRepository.save(user)                .doOnError(error -> status.setRollbackOnly());        });    }}运行本项目java运行7.3 扩展阅读建议7.3.1 官方文档与规范Spring Framework Reference Documentation - Transaction ManagementJDBC Transaction Management7.3.2 深度技术文章《Spring事务管理源码深度解析》《分布式事务解决方案对比》《数据库事务隔离级别实现原理》7.3.3 相关技术栈MyBatis事务集成:深入了解MyBatis与Spring事务的集成机制JPA事务管理:掌握JPA规范下的事务管理特性分布式事务框架:Seata、Saga、TCC等分布式事务解决方案7.4 实践项目建议7.4.1 基础练习项目// 项目1:银行转账系统// 功能要求:// 1. 实现账户间转账功能// 2. 支持并发转账处理// 3. 实现转账记录和审计日志// 4. 处理各种异常情况@Servicepublic class BankTransferProject {        @Transactional(isolation = Isolation.SERIALIZABLE)    public TransferResult transfer(TransferRequest request) {        // 实现转账逻辑        // 考虑并发控制、异常处理、审计日志等    }}运行本项目java运行7.4.2 进阶挑战项目// 项目2:电商订单系统// 功能要求:// 1. 订单创建涉及多个服务(库存、支付、物流)// 2. 实现分布式事务管理// 3. 支持订单状态机和补偿机制// 4. 性能优化和监控@GlobalTransactionalpublic class ECommerceOrderProject {        public OrderResult createOrder(OrderRequest request) {        // 实现复杂的订单创建流程        // 涉及多个微服务的协调    }}运行本项目java运行7.5 技术发展趋势7.5.1 云原生事务管理随着微服务和云原生架构的普及,事务管理正朝着以下方向发展:Saga模式:长事务的分解和补偿机制事件驱动架构:基于事件的最终一致性服务网格集成:Istio等服务网格中的事务管理7.5.2 响应式编程支持Spring WebFlux和响应式编程的兴起带来了新的事务管理需求:非阻塞事务:基于响应式流的事务处理背压处理:在事务中处理背压和流控异步事务协调:跨异步边界的事务管理7.6 讨论与思考7.6.1 开放性问题性能vs一致性:在高并发场景下,如何平衡事务的性能和数据一致性?微服务事务:在微服务架构中,是否应该避免跨服务事务?有哪些替代方案?事务边界设计:如何设计合理的事务边界来平衡业务完整性和系统性能?7.6.2 实践挑战遗留系统改造:如何将传统的事务管理代码迁移到Spring事务管理?测试策略:如何有效测试事务相关的代码,特别是异常场景?监控和诊断:如何监控事务性能和诊断事务相关问题?7.7 结语Spring事务管理是企业级Java开发中的核心技能,掌握它不仅需要理解理论知识,更需要在实践中不断积累经验。本文提供了从基础到高级的全面指南,但技术的学习永无止境。希望读者能够:持续实践:在实际项目中应用所学知识深入研究:探索更深层次的实现原理分享交流:与同行分享经验和最佳实践关注发展:跟上技术发展的最新趋势记住,优秀的事务管理不仅仅是技术实现,更是对业务逻辑的深刻理解和对系统架构的整体把握。让我们在Spring事务管理的道路上持续前进,构建更加健壮、高效的企业级应用————————————————原文链接:https://blog.csdn.net/weixin_63944437/article/details/153634279
  • [技术干货] 在 Java 中使用 MQTT:实现高效的消息传递-转载
    引言MQTT(Message Queuing Telemetry Transport)是一种轻量级的消息传输协议,专为低带宽、高延迟或不稳定的网络环境设计。它广泛应用于物联网(IoT)、机器对机器(M2M)通信以及实时数据传输等领域。本文将详细介绍如何在 Java 中使用 MQTT 协议实现高效的消息传递。为什么选择 MQTT?MQTT 具有以下优点:轻量级:MQTT 协议头非常小,减少了网络带宽的占用。低延迟:MQTT 支持发布/订阅模式,消息传递速度快。可靠性:MQTT 提供了三种服务质量(QoS)级别,确保消息的可靠传递。灵活性:MQTT 支持保留消息、遗嘱消息等功能,满足不同场景的需求。MQTT 基础概念在深入 Java 实现之前,我们需要了解一些 MQTT 的基本概念:Broker:消息代理,负责接收发布者发送的消息并将其分发给订阅者。Publisher:消息发布者,负责将消息发送到 Broker。Subscriber:消息订阅者,负责从 Broker 接收消息。Topic:消息的主题,订阅者通过订阅特定的主题来接收消息。QoS:服务质量级别,MQTT 提供了 0、1、2 三个级别,分别代表不同的可靠性。在 Java 中使用 MQTT要在 Java 中使用 MQTT,我们可以使用 Eclipse Paho 库。Paho 是一个开源的 MQTT 客户端库,支持多种编程语言,包括 Java。1. 添加依赖首先,在项目的 pom.xml 文件中添加 Paho MQTT 客户端的依赖:代码语言:xmlAI代码解释<dependency>    <groupId>org.eclipse.paho</groupId>    <artifactId>org.eclipse.paho.client.mqttv3</artifactId>    <version>1.2.5</version></dependency>AI写代码2. 创建 MQTT 客户端接下来,我们创建一个 MQTT 客户端实例,并连接到 MQTT Broker:代码语言:javaAI代码解释import org.eclipse.paho.client.mqttv3.*;import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; public class MqttExample {    public static void main(String[] args) {        String broker = "tcp://broker.hivemq.com:1883";        String clientId = "JavaSample";        MemoryPersistence persistence = new MemoryPersistence();         try {            MqttClient client = new MqttClient(broker, clientId, persistence);            MqttConnectOptions connOpts = new MqttConnectOptions();            connOpts.setCleanSession(true);             System.out.println("Connecting to broker: " + broker);            client.connect(connOpts);            System.out.println("Connected");             // 订阅主题            String topic = "test/topic";            client.subscribe(topic);             // 发布消息            String content = "Hello MQTT";            int qos = 2;            MqttMessage message = new MqttMessage(content.getBytes());            message.setQos(qos);            client.publish(topic, message);             // 断开连接            client.disconnect();            System.out.println("Disconnected");        } catch (MqttException me) {            System.out.println("reason " + me.getReasonCode());            System.out.println("msg " + me.getMessage());            System.out.println("loc " + me.getLocalizedMessage());            System.out.println("cause " + me.getCause());            System.out.println("excep " + me);            me.printStackTrace();        }    }}AI写代码3. 处理消息回调为了接收和处理从 Broker 发送的消息,我们需要实现 MqttCallback 接口,并将其设置为 MQTT 客户端的回调:代码语言:javaAI代码解释import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;import org.eclipse.paho.client.mqttv3.MqttCallback;import org.eclipse.paho.client.mqttv3.MqttMessage; public class MqttExample implements MqttCallback {    @Override    public void connectionLost(Throwable cause) {        System.out.println("Connection lost");        cause.printStackTrace();    }     @Override    public void messageArrived(String topic, MqttMessage message) throws Exception {        System.out.println("Message arrived: " + new String(message.getPayload()));    }     @Override    public void deliveryComplete(IMqttDeliveryToken token) {        System.out.println("Delivery complete");    }     // 其他代码...}AI写代码4. 运行示例将上述代码整合到一个完整的 Java 类中,并运行该类。你将看到以下输出:代码语言:javaAI代码解释Connecting to broker: tcp://broker.hivemq.com:1883ConnectedMessage arrived: Hello MQTTDisconnectedAI写代码总结通过本文,我们了解了如何在 Java 中使用 MQTT 协议实现高效的消息传递。我们介绍了 MQTT 的基本概念,展示了如何使用 Eclipse Paho 库创建 MQTT 客户端,订阅主题,发布消息以及处理消息回调。MQTT 在物联网和实时数据传输领域具有广泛的应用前景。————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/weixin_43044226/article/details/153143643
  • [技术干货] Java外功基础(1)——Spring Web MVC-转载
    1.前置知识1.1 Tomcat定义:Tomcat是一个开源的轻量级Web(Http)服务器和Servlet容器。它实现了Java Servlet等Java EE规范的核心功能,常用于部署和运行Java Web应用程序 。换言之,Tomcat就是一个严格遵循Servlet规范开发出来的、可以独立安装和运行的Java Web服务器/Servlet容器核心功能:Servlet容器:支持Servlet的执行,处理HTTP请求和响应Web服务器:提供静态资源(如HTML)的访问能力,支持基本的HTTP服务安装与版本对应:tomcat官网:Apache Tomcat®目录结构:bin:存放可执行文件,如startup.batconf:存放配置文件lib:存放Tomcat运行所需的jar文件logs:存储日志文件temp:存放临时文件,如上传的文件或缓存数据webapps:默认web应用部署目录work:服务器的工作目录,存放运行时生成的临时文件(编译文件)1.2 Servlet1.2.1 定义Servlet是Java语言编写的、运行在服务器端的程序,它遵循一套标准的API规范(Tomcat是这套规范的一个具体实现/容器,并提供了让Servlet与前端交互的运行时环境)1.2.2 API示范创建项目/配置文件:(1)在IEDA中创建Maven项目(2)在pom.xml文件中添加servlet依赖(置于< project >< /project >标签下)<dependencies>        <dependency>            <groupId>jakarta.servlet</groupId>            <artifactId>jakarta.servlet-api</artifactId>            <!--servlet依赖版本应与jdk和tomcat的版本相匹配-->            <version>6.1.0</version>            <scope>provided</scope>        </dependency></dependencies>(3)在main路径下创建webapp/Web-INF/web.xml,在xml文件中添加以下内容<!DOCTYPE web-app PUBLIC        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"        "http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app>    <display-name>Archetype Created Web Application</display-name></web-app>(4)下载插件:Smart Tomcat(为了方便启动项目)API示例:import jakarta.servlet.annotation.WebServlet;import jakarta.servlet.http.HttpServlet;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import java.io.IOException;//设置访问路径(url)@WebServlet("/method")//继承HttpServlet并重写doGet和doPost方法public class MethodServlet extends HttpServlet {    //接收method=post的请求    @Override    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {        System.out.println("doPost");        resp.setContentType("text/html; charset=utf-8");        resp.getWriter().write("doPost");    }    //接收method=put的请求    @Override    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException {        System.out.println("doPut");        resp.setContentType("text/html; charset=utf-8");        resp.getWriter().write("doPut");    }    //接收method=delete的请求    @Override    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException {        System.out.println("doDelete");        resp.setContentType("text/html; charset=utf-8");        resp.getWriter().write("doDelete");    }}1.2.3 生命周期定义:Servlet 生命周期由 Web容器(如Tomcat)管理,包含加载、初始化、处理请求和销毁四个阶段。每个阶段对应特定的方法调用,开发者可通过重写这些方法实现自定义逻辑1.类加载:Web容器通过类加载器加载 Servlet 类(通常首次请求触发或容器启动时预加载,字节码文件被加载到内存但未实例化)。具体类加载流程请阅读:Java虚拟机——JVM(JavaVirtualMachine)解析一1.5 实例化:确认Servlet类成功加载后立刻执行,在整个Web容器中每种Servlet类(如HttpServlet)只会有一个实例化对象2.初始化:Web容器调用刚刚创建好的Servlet实例的init(ServletConfig config)方法,在整个servlet实例的生命周期中仅调用一次,主要作用是读取配置和资源加载。若初始化失败,抛出ServletException,Servlet不会被加入可用队列3.处理请求:Web容器为每个请求创建线程,调用service(ServletRequest req, ServletResponse res)方法。service() 方法根据HTTP请求类型(get/post)调用doGet()或doPost()4.销毁:Web容器调用 destroy()方法,Servlet 实例被标记为垃圾回收2.SpringBootServlet是Java EE规范中处理Web请求的核心组件,但随着应用复杂度提升,Servlet的直接使用显得笨重。Spring框架通过一系列抽象和扩展,简化了企业级应用开发我们可以用一个非常形象的比喻来贯穿始终:建造一座房子第一阶段:Servlet 时代 - 自己烧砖砌墙目标: 建造一个能遮风挡雨的房子(一个能处理HTTP请求的Web应用)你的工作状态:材料: 你有最基础的原材料——泥土(Java 语言)和水(JVM)。你需要自己烧制砖块(编写Servlet类)工具: 只有简单的泥瓦刀(Servlet API)过程:1.你为每一面墙、每一扇门都亲手烧制一块特定的砖(编写 LoginServlet, UserServlet, OrderServlet)2.你亲自规划每块砖的位置(在web.xml中配置大量的 < servlet > 和 < servlet-mapping >)3.你亲自搅拌水泥,一块一块地砌墙(在每个 Servlet 的doGet/doPost 方法中手动解析参数、处理业务、组装 HTML)核心特点:高度灵活: 你可以造出任何形状的砖极其繁琐: 大量重复性劳动(每个 Servlet 都有获取参数、关闭连接等样板代码)难以维护: 砖块之间紧密耦合(对象依赖硬编码),想换一扇窗(修改功能)可能会牵动整面墙依赖外部: 房子建在别人的地上(需要将war包部署到外部的Tomcat服务器)总结:Servlet提供了Web开发的基础能力,但开发效率极低,代码冗余且难以维护第二阶段:Spring 时代 - 使用预制件和设计图纸目标: 用更高效、更标准化的方式建造一个结构更好、更易扩展的房子你的工作状态:材料: 你不再烧砖,而是使用工厂提供的标准化预制件(Spring Bean,如 @Controller, @Service)核心创新: 你聘请了一位神奇的管家(IoC 容器)你不再亲自“砌砖”(用new实例化对象),只需告诉管家你需要什么(用@Autowired声明依赖)管家会自动把预制件(Bean)按照图纸(配置)组装好,送到你手上(依赖注入DI)过程:1.一个总大门(DispatcherServlet): 房子只有一个入口,所有访客(请求)都先到这里2.管家调度: 总大门处的接待员(DispatcherServlet)根据访客需求,呼叫房子里对应的专业房间(@Controller中的方法)来接待3.开发者只需专注于房间内的专业服务(业务逻辑),而不用关心访客是怎么进来的核心特点:解耦: 预制件之间是松耦合的,易于更换和测试专业化: AOP(面向切面编程)可以像“装修队”一样,非侵入式地为所有房间统一安装中央空调(日志、安全、事务)效率提升: 避免了大量重复劳动,结构清晰配置复杂: 绘制详细的“组装图纸”(配置 Spring)本身成了一项复杂的工作总结:Spring框架通过IoC/DI和AOP等理念,解决了代码耦合和重复劳动问题,但引入了显著的配置复杂度Spring Boot 1.0.0正式发布于2014年4月1日,标志着该框架的首次稳定版本发布。SpringBoot基于SpringFramework 4进行设计,显著减少了开发者的配置工作量,彻底消除了Spring的配置地狱约定大于配置:约定了默认配置Start机制:是一种依赖管理机制,每个Starter包含特定功能所需的依赖库和自动配置类,开发者只需引入对应Starter即可快速启用功能模块嵌入式容器:内置了Tomcat等嵌入式容器,无需部署war文件到外部容器,直接运行即可启动应用3.Spring Web MVC3.1 概述官方描述:Spring Web MVC是基于Servlet API构建的原始Web框架,并从一开始就在 Spring框架中。正式名称“Spring Web MVC”, 来自其源模块的名称(spring-webmvc),但它通常被称为“Spring MVC”MVC的起源与发展:MVC(Model-View-Controller)模式最初由挪威计算机科学家Trygve Reenskaug于1978年在施乐帕克研究中心(Xerox PARC)提出,目的是为Smalltalk编程语言设计用户界面。其核心思想是将应用程序的逻辑分为三个独立组件:Model:处理数据逻辑和业务规则View:负责数据展示和用户界面Controller:接收用户输入并协调Model与View的交互Spring MVC与MVC的关系:Spring MVC是MVC模式在Spring框架中的具体化,同时扩展了传统MVC的功能以适应现代Web开发需求3.2 必需工具Postman:主要用于 API 的开发和测试。它提供了一个用户友好的界面,支持发送HTTP请求、管理请求历史、自动化测试以及团队协作下载地址:Download PostmanFiddler:是一个网络调试代理工具,主要用于监控和分析HTTP/HTTPS流量。它可以捕获设备与服务器之间的所有请求和响应,支持修改请求、重放请求以及性能分析下载地址:Fiddler - Download3.3 RequestMapping作用:是Spring MVC中最核心、最基础的注解之一,用于将HTTP请求映射到具体的方法上注解级别:类+方法作为类注解:可以为整个类提供一个统一的url前缀(可有可无)作为方法注解:指定该方法负责处理哪个url的请求(强制要求)@RequestMapping("/HelloController")//@RestController声明该类是一个Spring MVC控制器@RestControllerpublic class HelloController {    //0.不接收参数    //作为 方法注解 时,@RequestMapping(value = "/hello",method = RequestMethod.GET)等同于@GetMapping(value = "/hello")    //设置返回的响应是json格式,produces = "application/json"    @RequestMapping(value = "/hello",method = RequestMethod.GET,produces = "application/json")    public String hello() {        return "{\"Hello\" : World}";    }    //1.一个参数    @RequestMapping("/receiveAge1")    //不传参或者传递的参数名不匹配时默认为null    public String receiveAge1(Integer age) {        return "接收到参数 age:" + age;    }    @RequestMapping("/receiveAge2")    //不传参或者传递的参数名不匹配时尝试设置为null,但int无法被设置为null,所以抛出IllegalStateException    public String receiveAge2(int age) {        return "接收到参数 age:" + age;    }    //2.接收数组    @RequestMapping("/receiveArray")    public String receiveArray(String[] array) {        return "接收到参数 array:" + Arrays.toString(array);    }    //3.接收对象,需要保证传递的参数名称和数量与Java对象保持一致    @RequestMapping("/receivePerson")    public String receivePerson(Person person) {        return "接收到参数 person:" + person;    }}3.4 RequestBody作用:将HTTP请求体中的json数据绑定到Java对象(方法注解)注解级别:方法    @RequestMapping("/receivePerson")    //@RequestBody接收JSON格式的数据    public String receivePerson(@RequestBody Person person) {        return "接收到参数 person:" + person;    }3.5 RequestParam作用:是Spring MVC框架中从HTTP请求中提取参数/查询字符串的注解,主要用于将请求参数绑定到控制器方法的参数上注解级别:方法    @RequestMapping("/receiveRename")    //@RequestParam将url中key=name的查询字符串绑定到控制器的userName参数上    //required = false设置该参数为非必传(默认为true,必传)    public String receiveRename(@RequestParam(value = "name",required = false) String userName) {        return "接收到参数name:" + userName;    }注意:需要接收多个同名参数时(如param=value1¶m=value2),直接绑定到List类型需通过该注解明确声明    @RequestMapping("/receiveList1")    public String receiveList1(ArrayList<String> list) {        //返回的list为空        return "接收到参数 list:" + list;(1)在Spring MVC中,参数绑定机制对集合类型和数组类型的处理存在差异(2)使用ArrayList< String >作为方法参数时,必须显式添加@RequestParam注解,原因如下:默认绑定规则:Spring默认将单个请求参数的值绑定到简单类型(如 String、int)或单个对象。对于集合类型,框架无法自动推断是否需要将多个同名参数合并为集合需要明确指示:@RequestParam注解会告知Spring将同名请求参数的值收集到一个集合中(3)数组(如 String[])无需 @RequestParam 注解即可正确接收,原因如下:内置支持:Spring对数组类型有原生支持,能自动将多个同名请求参数值绑定到数组。这是框架的默认行为,无需额外配置    @RequestMapping("/receiveList2")    public String receiveList2(@RequestParam(required = false) ArrayList<String> list) {        //正确返回        return "接收到参数 list:" + list;    }    @RequestMapping("/receiveList3")    public String receiveList3(List<String> list) {        //报错        return "接收到参数 list:" + list;    }后端报错:java.lang.IllegalStateException: No primary or single unique constructor found for interface java.util.List。receiveList3方法使用List< String >接口类型而非具体实现类。Spring虽然支持接口类型参数绑定,但需要满足特定条件:必须配合@RequestParam注解使用不能直接使用未注解的接口类型参数报错根本原因:Spring尝试实例化List接口失败(接口不可实例化)3.6 PathVariable作用:用于从URL路径中提取变量值并绑定到方法的参数上注解级别:方法    @RequestMapping("/receivePath/{article}/{blog}")    //required = false设置该参数为非必传(默认为true,必传)    public String receivePath(@PathVariable(value = "article",required = false)Integer title,@PathVariable(value = "blog",required = false)String content) {        return "接收到参数 article:" + title + " blog:" + content;    }3.7 RequestPart作用:用于处理 HTTP 请求中的 multipart/form-data 类型数据,通常用于文件上传或同时上传文件和其他表单字段的场景注解级别:方法    @RequestMapping("/receiveFile")    public String receiveFile(@RequestPart(value = "file",required = false) MultipartFile imgFile,@RequestParam(value = "userName",required = false) String name) {        //返回原始的文件名        return "用户:" + name+ ",接收到文件:" +imgFile.getOriginalFilename();    }3.8 Controller&ResponseBody&RestControllerController作用:是Spring MVC中的核心注解,用于标记一个类作为Web请求的处理器(声明一个类是一个Spring MVC控制器),负责处理HTTP请求并返回视图注解级别:类ResponseBody作用:指示方法返回值应直接写入HTTP响应体,而非通过视图解析器渲染注解级别:类+方法@RequestMapping("/ControllerResponse")@Controllerpublic class ControllerResponse {    //返回视图    @RequestMapping("/HTMLView")    public String HTMLView(){        return "/show.html";    }    //返回数据    @ResponseBody    @RequestMapping("/HTMLData")    public String HTMLData(){        return "/show.html";    }}RestController作用:是Spring MVC中的一个组合注解,它结合了@Controller和@ResponseBody的功能,标记的类所有方法返回值默认直接作为 HTTP 响应体(JSON/XML 等格式),无需额外视图渲染注解级别:类4.GiteeGitee地址:九转苍翎本文源码:SpringBoot_Demo1————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/2401_89167985/article/details/148194312
  • [技术干货] 中秋满月皆十六圆?Java实证求解后的真相-转载
    前言        自古以来,中秋佳节便与圆月紧密相连,成为人们寄托思念与团圆之情的象征。在民间流传着这样一种说法:“十五的月亮十六圆”,仿佛这已成为一种铁律,深入人心。然而,这种说法是否真的站得住脚呢?在这背后,隐藏着怎样的天文奥秘?又是否可以通过科学的方法来验证这一传统观念呢?在科技飞速发展的今天,我们不妨借助编程的力量,运用Java语言来实证求解,揭开中秋满月的真相。         中秋赏月的传统由来已久,早在《周礼》中就有“中秋夜迎寒”的记载,而到了唐代,中秋赏月、玩月的风俗开始盛行。文人墨客们更是留下了许多描写中秋月夜的佳作,如苏轼的“但愿人长久,千里共婵娟”,将中秋的月与人间的思念紧密相连,赋予了中秋月深厚的文化内涵。在这样的文化背景下,“十五的月亮十六圆”这一说法也逐渐流传开来,成为人们茶余饭后的话题之一。然而,这种说法真的准确无误吗?        本文通过Java实证求解中秋满月的时间,不仅可以验证传统的说法,还可以更深入地了解天文学中的相关知识。这不仅是一次对传统观念的挑战,也是一次对科学方法的实践。无论最终的结果如何,这一过程都将让我们对中秋满月有更深刻的认识,也将让我们感受到科学的魅力和力量。在接下来的章节中,我们将详细介绍如何使用Java语言进行天文数据的处理和计算,以及如何通过模拟实验来验证“十五的月亮十六圆”这一说法。我们将逐步展开这一探索之旅,最终揭示中秋满月的真相。让我们一起踏上这段充满趣味和挑战的旅程,用科学的视角重新审视中秋的圆月,探索其中隐藏的奥秘。一、天文上的满月        在天文学中,月亮的圆缺变化是一个非常有趣且复杂的自然现象,这种变化主要源于月球绕地球的公转运动。月球绕地球运行一周的时间大约是29.5天,这个周期被称为一个“朔望月”。在这个周期中,月球相对于太阳的位置不断变化,从而导致我们从地球上看到的月相也随之改变。博主不是专业天文专业,这里仅分享一些简单的满月基础知识,让大家有一个概念。1、形成原理及定义        说到满月就必须提及月相,月相的形成是由于太阳光照射月球的不同部分,而我们从地球上看到的只是月球被太阳照亮的那一部分。随着月球绕地球的公转,被太阳照亮的部分逐渐增加,依次出现“娥眉月”“上弦月”“凸月”“满月”“下弦月”“残月”等不同的月相。其中满月是指月球完全被太阳照亮的那一面朝向地球,此时月球与太阳在地球的两侧,三者几乎在一条直线上。理论上,满月应该出现在农历的十五或十六,但实际的情况并非总是如此。由于月球的公转轨道是椭圆形的,且受到多种因素的影响,如地球的引力、太阳的引力等,月球的实际运行轨迹并非完全规律,因此满月出现的时间也会有所变化。2、出现时间及观测        “十五的月亮十六圆”这一说法广为流传,但实际上满月并不总是出现在农历的十六。根据天文观测数据,满月可能出现在农历的十四到十七之间的任何一天。例如,在某些年份,满月可能出现在农历十四的晚上,而在另一些年份,满月可能出现在农历十七的早晨。这种变化是由于月球的公转速度和轨道形状的不规则性所导致的。满月是观测月球的最佳时机之一,因为此时月球的整个盘面都被照亮,可以清晰地看到月球表面的山脉、陨石坑和月海等特征。在满月期间,月球的亮度会达到最大,这使得它在夜空中格外明亮。3、文化意义        在许多文化中,满月都具有重要的象征意义。在中国文化中,满月象征着团圆和完满,因此中秋节成为了家人团聚的重要节日。在西方文化中,满月也常常与神秘和浪漫联系在一起,许多文学作品和民间传说都以满月为背景。二、Java模拟月满计算        随着计算机技术的发展,我们有了更强大的工具来探索和验证这些天文现象。Java作为一种广泛使用的编程语言,具有强大的功能和灵活性,可以用来编写各种复杂的算法和程序。在本研究中,我们将利用Java语言编写程序,通过计算月球在不同时间的位置,来确定中秋满月的具体时间。我们将收集多年来的天文数据,包括月球的公转周期、轨道参数等,然后利用这些数据进行模拟计算。通过这种方式,我们可以得到一个较为准确的中秋满月时间表,从而验证“十五的月亮十六圆”这一说法的准确性。1、整体实现逻辑        使用Java求解中秋满月整体时间逻辑如下:public class MidAutumnFullMoonCalculator {    // 主计算方法    public static Date calculateFullMoonTime(int year, int month, int day) { ... }        // 核心天文算法    private static double calculateFullMoonJulianDay(double jd) { ... }        // 辅助方法    private static double normalizeAngle(double angle) { ... }    private static double calendarToJulianDay(Calendar cal) { ... }    private static Calendar julianDayToCalendar(double jd) { ... }}AI写代码java运行2、主计算方法详解        功能:这是程序的入口点,接收农历中秋的公历日期,返回精确的满月时刻,核心方法如下:/** * -计算指定农历中秋日期的月亮最圆时刻 * @param year 年份 * @param month 农历月份(八月) * @param day 农历日期(十五) * @return 月亮最圆时刻的Date对象 */public static Date calculateFullMoonTime(int year, int month, int day) {   // 创建农历中秋日期(使用中午12点作为基准时间)   Calendar midAutumnDate = Calendar.getInstance();   midAutumnDate.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));   midAutumnDate.set(year, month - 1, day, 12, 0, 0); // month-1因为Calendar月份从0开始   // 计算精确的满月时刻   return calculatePreciseFullMoonTime(midAutumnDate);}AI写代码java运行        参数说明:year:公历年份(如2024)month:公历月份(如9)day:公历日期(如17)        处理流程:创建Calendar对象,设置为北京时间将时间设为中午12点作为计算基准调用核心算法计算精确的满月时刻3、核心天文算法详解        核心算法是相关计算中最核心的内容,主要包括儒略日的计算、时间参数的计算、天文参数计算和周期项修正等内容,这里的天文计算采用近似计算,如需精度计算,请使用更精准的天文算法。3.1 儒略日计算基础/** * -计算满月时刻的儒略日 * -基于Jean Meeus的天文算法 */private static double calculateFullMoonJulianDay(double jd) {    // 计算从2000年1月6日(基准新月)开始的月相周期数    double k = Math.floor((jd - 2451550.09765) / 29.530588853);    // 满月对应的k值(新月+0.5)    k = k + 0.5;    // 计算T(儒略世纪数)    double T = k / 1236.85;    //----其它计算}AI写代码java运行        儒略日(Julian Day):天文学中常用的连续时间计数法,从公元前4713年1月1日格林尼治平午开始计算。月相周期数k:2451550.09765:2000年1月6日18:14的儒略日,作为一个基准新月时刻29.530588853:一个朔望月的平均长度(天)k:从基准时间开始经过的月相周期数k + 0.5:从新月到满月是半个周期3.2 时间参数计算// 计算T(儒略世纪数)double T = k / 1236.85;        // 计算基础儒略日double JDE = 2451550.09765 + 29.530588853 * k         + 0.0001337 * T * T         - 0.000000150 * T * T * T         + 0.00000000073 * T * T * T * T;AI写代码java运行        T(儒略世纪数):以36525天为一世纪的时间单位,用于高阶项的计算。        (儒略历书日):考虑了长期项修正的基础满月时刻。3.3 天文参数计算 // 计算太阳平近点角 double M = normalizeAngle(2.5534 + 29.10535669 * k            - 0.0000218 * T * T            - 0.00000011 * T * T * T);        // 计算月亮平近点角double Mprime = normalizeAngle(201.5643 + 385.81693528 * k                 + 0.1017438 * T * T                 + 0.00001239 * T * T * T                 - 0.000000058 * T * T * T * T);        // 计算月亮升交点平黄经double F = normalizeAngle(160.7108 + 390.67050274 * k             - 0.0016341 * T * T             - 0.00000227 * T * T * T             + 0.000000011 * T * T * T * T);        // 计算Omega(月亮轨道升交点经度)double Omega = normalizeAngle(124.7746 - 1.56375580 * k              + 0.0020691 * T * T              + 0.00000215 * T * T * T);AI写代码java运行        天文参数说明:M(太阳平近点角):太阳在轨道上的平均位置角度系数:2.5534° + 29.10535669°/周期反映地球公转轨道的椭圆性影响M'(月亮平近点角):月亮在轨道上的平均位置角度系数:201.5643° + 385.81693528°/周期反映月球公转轨道的椭圆性影响F(月亮升交点平黄经):月球轨道与黄道交点的平均位置系数:160.7108° + 390.67050274°/周期反映月球轨道平面的进动Ω(月亮轨道升交点经度):更精确的轨道交点位置系数:124.7746° - 1.56375580°/周期3.4 周期项修正计算// 转换为弧度double M_rad = Math.toRadians(M);double Mprime_rad = Math.toRadians(Mprime);double F_rad = Math.toRadians(F);double Omega_rad = Math.toRadians(Omega);// 计算周期项修正double correction = 0;// 主要修正项correction += -0.40720 * Math.sin(Mprime_rad);correction += 0.17241 * 0.016708617 * Math.sin(M_rad);correction += 0.01608 * Math.sin(2 * Mprime_rad);correction += 0.01039 * Math.sin(2 * F_rad);correction += 0.00739 * 0.016708617 * Math.sin(Mprime_rad - M_rad);correction += -0.00514 * 0.016708617 * Math.sin(Mprime_rad + M_rad);correction += 0.00208 * 0.016708617 * 0.016708617 * Math.sin(2 * M_rad);correction += -0.00111 * Math.sin(Mprime_rad - 2 * F_rad);correction += -0.00057 * Math.sin(Mprime_rad + 2 * F_rad);correction += 0.00056 * 0.016708617 * Math.sin(2 * Mprime_rad + M_rad);correction += -0.00042 * Math.sin(3 * Mprime_rad);correction += 0.00042 * 0.016708617 * Math.sin(M_rad + 2 * F_rad);correction += 0.00038 * 0.016708617 * Math.sin(M_rad - 2 * F_rad);correction += -0.00024 * 0.016708617 * Math.sin(2 * Mprime_rad - M_rad);correction += -0.00017 * Math.sin(Omega_rad);correction += -0.00007 * Math.sin(Mprime_rad + 2 * M_rad);correction += 0.00004 * Math.sin(2 * Mprime_rad - 2 * F_rad);correction += 0.00004 * Math.sin(3 * M_rad);correction += 0.00003 * Math.sin(Mprime_rad + M_rad - 2 * F_rad);correction += 0.00003 * Math.sin(2 * Mprime_rad + 2 * F_rad);correction += -0.00003 * Math.sin(Mprime_rad + M_rad + 2 * F_rad);correction += 0.00003 * Math.sin(Mprime_rad - M_rad + 2 * F_rad);correction += -0.00002 * Math.sin(Mprime_rad - M_rad - 2 * F_rad);correction += -0.00002 * Math.sin(3 * Mprime_rad + M_rad);correction += 0.00002 * Math.sin(4 * Mprime_rad);       // 应用修正double preciseJDE = JDE + correction;AI写代码java运行修正项原理:每个修正项都对应一个特定的天文效应:-0.40720 × sin(M'):月球椭圆轨道的主要修正(中心差)0.17241 × e × sin(M):地球轨道偏心率对月相的影响0.01608 × sin(2M'):月球轨道的二阶椭圆项0.01039 × sin(2F):月球轨道倾角的影响0.00739 × e × sin(M' - M):地球和月球轨道相互影响-0.00514 × e × sin(M' + M):地球和月球轨道的组合效应e = 0.016708617:地球轨道偏心率这些修正项基于布朗月球运动理论,考虑了月球轨道的各种摄动因素。4、辅助方法详解        本小节将对辅助方法进行简单介绍。4.1 角度标准化/** * -将角度标准化到0-360度范围内 */private static double normalizeAngle(double angle) {   angle = angle % 360;   if (angle < 0) {       angle += 360;   }   return angle;}AI写代码java运行        功能:将角度限制在0-360度范围内,避免数值溢出。4.2 日历与儒略日转换/** * -将Calendar转换为儒略日 */private static double calendarToJulianDay(Calendar cal) {   int year = cal.get(Calendar.YEAR);   int month = cal.get(Calendar.MONTH) + 1;   int day = cal.get(Calendar.DAY_OF_MONTH);   int hour = cal.get(Calendar.HOUR_OF_DAY);   int minute = cal.get(Calendar.MINUTE);   int second = cal.get(Calendar.SECOND);   double decimalHour = hour + minute / 60.0 + second / 3600.0;   if (month <= 2) {      year--;      month += 12;   }   int a = year / 100;   int b = 2 - a + a / 4;   return Math.floor(365.25 * (year + 4716)) + Math.floor(30.6001 * (month + 1))                 + day + decimalHour / 24.0 + b - 1524.5;}AI写代码java运行        转换公式:标准的天文儒略日计算公式,考虑了:闰年规则格里高利历改革(1582年)时间的小数部分处理4.3 儒略日转日历/** * -将儒略日转换为Calendar */private static Calendar julianDayToCalendar(double jd) {    jd += 0.5;    double z = Math.floor(jd);    double f = jd - z;    double a;    if (z < 2299161) {       a = z;    } else {       double alpha = Math.floor((z - 1867216.25) / 36524.25);       a = z + 1 + alpha - Math.floor(alpha / 4);    }     double b = a + 1524;    double c = Math.floor((b - 122.1) / 365.25);    double d = Math.floor(365.25 * c);    double e = Math.floor((b - d) / 30.6001);    double day = b - d - Math.floor(30.6001 * e) + f;    int month = (int) (e < 14 ? e - 1 : e - 13);    int year = (int) (month > 2 ? c - 4716 : c - 4715);     double time = day - Math.floor(day);    int hour = (int) (time * 24);    int minute = (int) ((time * 24 - hour) * 60);    int second = (int) Math.round((((time * 24 - hour) * 60 - minute) * 60));    // 处理秒数进位    if (second >= 60) {       second = 0;       minute++;    }    if (minute >= 60) {       minute = 0;       hour++;    }      Calendar cal = Calendar.getInstance();    cal.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));    cal.set(year, month - 1, (int) Math.floor(day), hour, minute, second);    cal.set(Calendar.MILLISECOND, 0);    return cal;}AI写代码java运行        关键点:jd += 0.5:儒略日从中午开始,调整为从午夜开始处理格里高利历改革(1582年10月4日后跳过10天)精确的时间分量计算三、近年中秋满月计算及对比        本节将结合实例对每年的中秋月满时间进行计算,通过本小节就可以获取每年的满月日期和具体的时间,并且与官方提供的时间进行对比,大家通过对比就可以知晓问题的开始,是不是所有的月亮都是十六圆了。        1、近年中秋满月计算/** * -测试方法 - 计算未来几年的中秋节月亮最圆时刻 */public static void main(String[] args) {   // 已知的农历中秋日期(公历日期)   int[][] midAutumnDates = {       {2019, 9, 13},  // 2019年中秋节       {2020, 10, 1},  // 2020年中秋节       {2021, 9, 21},  // 2021年中秋节       {2022, 9, 10},  // 2022年中秋节       {2023, 9, 29},  // 2023年中秋节       {2024, 9, 17},  // 2024年中秋节       {2025, 10, 6},  // 2025年中秋节       {2026, 9, 25},  // 2026年中秋节    };     SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");    sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));    System.out.println("中秋节月亮最圆时刻计算结果:");    System.out.println("=================================");    for (int[] date : midAutumnDates) {        int year = date[0];        int month = date[1];        int day = date[2];        Date fullMoonTime = calculateFullMoonTime(year, month, day);        System.out.printf("%d年中秋节(公历%d月%d日)月亮最圆时刻: %s%n",                 year, month, day, sdf.format(fullMoonTime));    }}AI写代码java运行        接下来我们在IDE中运行意以上成就可以得到以下结果:         以上就是实现一个从2019年到2026年,跨度为7年的中秋满月计算过程。2、近年计算与公布时间对比        通过以上7年的计算,再结合官方公布的满月日期及时刻,来对比一下我们的计算方法与官方公布的时间相差是多少?年份    中秋(公历)    满月时间(本地)    是否当天    满月时间(官方公布)    误差2019    2019-9-13    09月14日 08时39分21秒    否(十六)    9月14日12时33分    3时54分2020    2020-10-1    10月02日 01时28分17秒    否(十六)    10月2日 05时5分    3时37分2021    2021-9-21    09月21日 03时58分38秒    是(十五)    9月21日 07时54分‌    3时56分2022    2022-9-10    09月10日 13时34分12秒    是(十五)    9月10日 17时59分    4时25分2023    2023-9-29    09月29日 13时49分05秒    是(十五)    9月29日 17时58分    4时8分2024    2024-9-17    09月18日 06时15分39秒    否(十六)    9月18日 10时34分    4时19分2025    2024-10-06    10月07日 07时41分10秒    否(十六)    10月7日 11时48分    4时7分        结合近七年的满月日期及时刻来看,并不是所有的中秋月圆都是十六圆,有的是当天就圆了。所以,从这个角度来定义,十五的月亮十六圆可不是准确的哦。通过这种本地近似的计算,虽然在具体的时刻上有一些误差,但是日期是与官方公布的是完全一致的,时刻的误差通过近7年的验证,相差时间在4个小时左右,所以未来可以结合更长序列的时间进行相应的修正。四、总结        以上就是本文的主要内容,本文通过Java实证求解中秋满月的时间,不仅可以验证传统的说法,还可以更深入地了解天文学中的相关知识。这不仅是一次对传统观念的挑战,也是一次对科学方法的实践。无论最终的结果如何,这一过程都将让我们对中秋满月有更深刻的认识,也将让我们感受到科学的魅力和力量。通过Java满月近似求解,并结合2019年到2025年的中秋满月日期时刻的计算,得出了重要的一个结论,十五的月亮不一定十六圆,通过严谨的程序计算得到的数据支撑。行文仓促,定有不足之处,欢迎各位朋友在评论区批评指正,不胜感激。————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/yelangkingwuzuhu/article/details/152717752
  • [技术干货] Java外功精要(3)——Spring配置文件和mybatis-转载
    1.配置文件1.1 概述计算机配置文件:用于存储系统、应用程序的设置信息,通常以文本或结构化数据格式(如JSON、XML、INI等)保存。其核心功能包括但不限于:参数定制:允许用户或管理员调整软件或硬件的运行参数环境适配:根据不同设备或场景加载特定配置(如开发/生产环境)持久化存储:确保重启后设置仍生效SpringBoot配置文件:SpringBoot支持多种类型的配置文件,常见的格式包括properties、yaml和yml,主要用于集中管理应用程序的各种配置参数,简化部署和开发过程中的环境切换YAML和YML本质上是相同的文件格式,只是文件扩展名的不同,两者在功能和使用上没有区别1.2 propertiesproperties配置文件是最早期的配置⽂件格式,也是创建SpringBoot项⽬默认的配置⽂件采用常见的键值对格式(key=value)支持#开头的注释#应用程序名称spring.application.name=configuration#应用程序端口号server.port=8080#数据库连接信息spring.datasource.url=jdbc:mysql://127.0.0.1:3306/database_name?characterEncoding=utf8&useSSL=falsespring.datasource.username=rootspring.datasource.password=root1.3 yml采用键值对格式(key: value),冒号后必须有空格数据序列化格式,通过缩进表示层级关系支持#开头的注释spring:  application:    #应用程序名称    name: configuration  #数据库连接信息  datasource:    url: jdbc:mysql://127.0.0.1:3306/database_name?characterEncoding=utf8&useSSL=false    username: root    password: root#应用程序端口号server:  port: 80801.4 优缺点对比properties优点:语法简单直观,采用key=value形式,适合初学者快速上手与Java生态兼容性极强缺点:缺乏层次结构,复杂配置时容易冗余。上述配置数据库连接信息时spring.datasource前缀冗余不支持数据类型定义,所有值均为字符串,需手动转换yml优点:层次化结构清晰,通过缩进表示层级,适合复杂配置场景支持数据类型(如布尔值、数字),减少手动类型转换缺点:格式错误易导致解析失败(容易忽略冒号后空格)部分旧版工具链兼容性较差,需额外依赖解析库注:SpringBoot同时支持两种格式,混合使用时若key重复,properties优先级高于yml1.5 @Value注解作用:是Spring框架提供了一个@Value注解(org.springframework.beans.factory.annotation.Value),用于将外部配置文件中的值注入到Spring管理的Bean中示例:(properties和yml的读取方式相同)package org.example.configuration.config;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Configuration;@Configurationpublic class Config {    @Value("${spring.application.name}")    private String applicationName;    @Value("${server.port}")    private Integer port;    @Value("${spring.datasource.url}")    private String url;    @Value("${spring.datasource.username}")    private String username;    @Value("${spring.datasource.password}")    private String password;    public void print() {        System.out.println("applicationName=" + applicationName);        System.out.println("port=" + port);        System.out.println("url=" + url);        System.out.println("username=" + username);        System.out.println("password=" + password);    }}package org.example.configuration;import org.example.configuration.config.Config;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.ApplicationContext;@SpringBootApplicationpublic class ConfigurationApplication {    public static void main(String[] args) {        ApplicationContext context = SpringApplication.run(ConfigurationApplication.class, args);        Config config = context.getBean(Config.class);        config.print();    }}运行结果:applicationName=configurationport=8080url=jdbc:mysql://127.0.0.1:3306/database_name?characterEncoding=utf8&useSSL=falseusername=rootpassword=root2.mybatis2.1 概述MyBatis是一款优秀的持久层框架,支持自定义 SQL、存储过程、高级映射以及多种配置方式。它消除了几乎所有的JDBC代码和参数的手动设置以及结果集的检索支持存储过程:指的是数据库管理系统(DBMS)允许用户创建、存储和执行存储过程的能力。存储过程是一组预编译的SQL语句,存储在数据库中,可以被应用程序调用执行支持高级映射:指通过配置或注解实现复杂SQL查询结果与Java对象之间的灵活转换。其核心目标是简化数据库关联操作,提升开发效率支持多种配置方式:mybatis支持注解和xml两种配置方式2.2 前置操作引入依赖:Spring Web,Mybatis Framework,MySQL Driver,Lombok在application.properties/yml中添加数据库连接信息:#应用程序名称spring.application.name=configuration#应用程序端口号server.port=8080#数据库连接信息spring.datasource.url=jdbc:mysql://127.0.0.1:3306/database_name?characterEncoding=utf8&useSSL=falsespring.datasource.username=rootspring.datasource.password=root#自动驼峰转换mybatis.configuration.map-underscore-to-camel-case=truespring:  application:    #应用程序名称    name: configuration  #数据库连接信息  datasource:    url: jdbc:mysql://127.0.0.1:3306/database_name?characterEncoding=utf8&useSSL=false    username: root    password: root#应用程序端口号server:  port: 8080mybatis:  configuration:     map-underscore-to-camel-case: true #自动驼峰转换SQL命名规范:采用下划线分隔单词(如order_detail)Java命名规范:大驼峰/小驼峰2.3 注解2.3.1 配置1.创建一个接口,并使用 @Mapper注解 修饰import org.apache.ibatis.annotations.Mapper;@Mapperpublic interface BlogMapper {    //其他代码}@Mapper注解:允许开发者直接在接口方法上通过注解配置SQL语句,无需编写XML映射文件。适用于简单SQL场景,能显著减少配置量2.初始化数据create table blog (id int primary key auto_increment,name varchar(128),age int);insert into blog values (null,'刘备',30),(null,'关羽',28),(null,'张飞',25);3.创建对应实体类import lombok.Data;@Datapublic class PersonInfo {    private Integer id;    private String name;    private Integer age;    public PersonInfo(Integer id, String name, Integer age) {        this.id = id;        this.name = name;        this.age = age;    }    public PersonInfo() {    }}2.3.2 CRUDimport com.example.spring_mybatis.model.PersonInfo;import org.apache.ibatis.annotations.*;@Mapperpublic interface BlogMapper {    @Select("select * from blog")    List<PersonInfo> getPersonInfoAll();    @Insert("insert into blog values (#{id},#{name},#{age})")    Integer addPerson(PersonInfo person);    @Update("update blog set name = #{name},age = #{age} where id = #{id}")    Integer updatePerson(PersonInfo personInfo);    @Delete("delete from blog where id = #{id}")    Integer deletePerson(Integer id);}按住alt+insert,可在test目录下生成以上方法的测试方法import com.example.spring_mybatis.model.PersonInfo;import lombok.extern.slf4j.Slf4j;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import java.util.List;@SpringBootTest@Slf4jclass BlogMapperTest {    private final BlogMapper blogMapper;    @Autowired    public BlogMapperTest(BlogMapper blogMapper) {        this.blogMapper = blogMapper;    }    @Test    void getPersonInfoAll() {        List<PersonInfo> personInfoAll = blogMapper.getPersonInfoAll();        log.info("查询成功,personInfoAll:{}",personInfoAll.toString());        //查询成功,personInfoAll:[PersonInfo(id=1, name=刘备, age=30),        //PersonInfo(id=2, name=关羽, age=28),        //PersonInfo(id=3, name=张飞, age=25)]    }    @Test    void addPerson() {        Integer ret = blogMapper.addPerson(new PersonInfo(null, "赵云", 25));        log.info("添加成功,影响行数:{}",ret.toString());//添加成功,影响行数:1    }    @Test    void updatePerson() {        Integer ret = blogMapper.updatePerson(new PersonInfo(1, "刘备", 35));        log.info("更新成功,影响行数:{}",ret.toString());//更新成功,影响行数:1    }    @Test    void deletePerson() {        Integer ret = blogMapper.deletePerson(4);        log.info("删除成功,影响行数:{}",ret.toString());//删除成功,影响行数:1    }}2.3.3 @Param作用:用于在Mapper接口方法中为形式参数指定名称。当方法有多个参数时,通过该注解明确SQL中引用的参数名,避免依赖参数顺序@Mapperpublic interface BlogMapper {    //@Param    @Update("update blog set name = #{name},age = #{age} where id = #{id}")    Integer updatePersonInfo(@Param("id") Integer userId,@Param("name") String userName,@Param("age") Integer userAge);}AI写代码java运行123456@SpringBootTest@Slf4jclass BlogMapperTest {    private final BlogMapper blogMapper;    @Autowired    public BlogMapperTest(BlogMapper blogMapper) {        this.blogMapper = blogMapper;    }    @Test    void updatePersonInfo() {        Integer ret = blogMapper.updatePersonInfo(1, "刘玄德", 30);        log.info("更新成功,影响行数:{}",ret.toString());//更新成功,影响行数:1    }}2.4 xml2.4.1 配置1.创建一个接口,并使用 @Mapper注解 修饰import org.apache.ibatis.annotations.Mapper;@Mapperpublic interface BlogXMLMapper {    //其他代码}2.配置mybatis的xml文件路径mybatis:  mapper-locations: classpath:mybatis/**Mapper.xml #配置mybatis的xml文件路径  #标识位于resources/mybatis路径下任何以Mapper结尾的xml文件为mybatis的配置文件3.在resources/mybatis路径下创建以Mapper结尾的xml文件,并添加如下代码<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--namespace:用于指定该XML文件对应的Java接口或类的全限定名--><mapper namespace="com.example.spring_mybatis.mapper_blog.BlogXMLMapper"></mapper>2.4.2 示例在接口中声明方法import com.example.spring_mybatis.model_blog.PersonInfo;import org.apache.ibatis.annotations.Mapper;import java.util.List;@Mapperpublic interface BlogXMLMapper {    List<PersonInfo> getPersonInfoAll();}在对应xml文件中实现接口方法id:是MyBatis映射文件中SQL语句的唯一标识符。需与Mapper接口中的方法名一致,保证映射正确resultType:指定SQL查询结果映射的Java对象类型,需为全限定类名<mapper namespace="com.example.spring_mybatis.mapper_blog.BlogXMLMapper">    <select id="getPersonInfoAll" resultType="com.example.spring_mybatis.model_blog.PersonInfo">        select * from blog    </select></mapper>按住alt+insert,可在test目录下生成以上方法的测试方法import com.example.spring_mybatis.model_blog.PersonInfo;import lombok.extern.slf4j.Slf4j;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import java.util.List;@SpringBootTest@Slf4jclass BlogXMLMapperTest {    private final BlogXMLMapper blogXMLMapper;    @Autowired    public BlogXMLMapperTest(BlogXMLMapper blogXMLMapper) {        this.blogXMLMapper = blogXMLMapper;    }    @Test    void getPersonInfoAll() {        List<PersonInfo> personInfoAll = blogXMLMapper.getPersonInfoAll();        log.info("查询成功,personInfoAll:{}",personInfoAll.toString());    }}运行结果:2.5 动态SQL动态SQL:指在程序运行时根据条件或参数动态生成的SQL语句。与静态SQL相比,动态SQL更具灵活性,适用于需要根据不同条件构建查询的场景。例如,在某些web/app进行账号注册时会出现非必填选项mybatis的注解和xml两种方式都能实现动态SQL,但xml较为方便,所以下文使用xml来实现动态SQL2.5.1 trim标签作用:用于自定义字符串截取规则。包含四个属性:prefix:最终结果添加前缀suffix:最终结果添加后缀prefixOverrides:去除首部指定内容suffixOverrides:去除尾部指定内容2.5.2 if标签作用:用于条件判断,通常在where或set语句中使用。当test表达式的值为true时,包含标签内的SQL片段    <insert id="addPersonInfo">        insert into blog        <trim prefix="(" suffix=")" suffixOverrides=",">            <if test="id != null">                id,            </if>            <if test="name != null">                name,            </if>            <if test="age != null">                age,            </if>        </trim>        values        <trim prefix="(" suffix=")" suffixOverrides=",">            <if test="id != null">                #{id},            </if>            <if test="name != null">                #{name},            </if>            <if test="age != null">                #{age},            </if>        </trim>    </insert>2.5.3 where标签作用:替代SQL中的where关键字。当if条件成立时才会加入SQL片段,并自动去除第一个子句的and/or    <select id="getPersonInfoByNameAndAge">        select * from blog        <where>            <if test="name != null">                and name = #{name}            </if>            <if test="age != null">                and age = #{age}            </if>        </where>    </select>2.5.4 set标签作用:用于update语句。当if条件成立时才会加入SQL片段,并自动去除最后一个子句的逗号、    <update id="updatePersonInfo">        update blog        <set>            <if test="name != null">                name = #{name},            </if>            <if test="age != null">                age = #{age},            </if>        </set>        <where>            and id = #{id}        </where>    </update>2.5.5 foreach标签作用:用于集合遍历。主要属性:collection:集合参数名item:当前元素变量名open/close:包围符号separator:分隔符    @Test    void getPersonInfoById() {        ArrayList<Integer> ids = new ArrayList<>();        ids.add(1);        ids.add(2);        ids.add(3);        List<PersonInfo> personInfoById = blogXMLMapper.getPersonInfoById(ids);        System.out.println(personInfoById);    }    <select id="getPersonInfoById" resultType="com.example.spring_mybatis.model_blog.PersonInfo">        select * from blog        where id in        <foreach collection="ids" item="id" open="(" close=")" separator=",">            #{id}        </foreach>    </select>2.5.6 include标签作用:用于引用SQL片段,通过refid指定要引用的片段id。需配合sql标签使用,实现代码复用    <sql id="collection">        id,name,age    </sql>    <select id="getPersonInfoAll" resultType="com.example.spring_mybatis.model_blog.PersonInfo">        select        <include refid="collection">        </include>        from blog    </select>2.6 主键返回主键返回:指在数据库插入操作后,自动获取刚插入记录的主键值。在mybatis中使用注解和xml都能获取到返回的主键1.注解实现@Mapperpublic interface BlogMapper {    @Options(useGeneratedKeys = true,keyProperty = "id")    @Insert("insert into blog values (#{id},#{name},#{age})")    Integer addPerson(PersonInfo person);}@SpringBootTest@Slf4jclass BlogMapperTest {    private final BlogMapper blogMapper;    @Autowired    public BlogMapperTest(BlogMapper blogMapper) {        this.blogMapper = blogMapper;    }        @Test    void addPerson() {        PersonInfo personInfo = new PersonInfo(null, "黄忠", 60);        Integer ret = blogMapper.addPerson(personInfo);        log.info("添加成功,影响行数:{},返回的主键值:{}",ret.toString(),personInfo.getId());//添加成功,影响行数:1,返回的主键值:6    }}2.通过xml实现@SpringBootTest@Slf4jclass BlogXMLMapperTest {    private final BlogXMLMapper blogXMLMapper;    @Autowired    public BlogXMLMapperTest(BlogXMLMapper blogXMLMapper) {        this.blogXMLMapper = blogXMLMapper;    }        @Test    void addPersonInfo() {        PersonInfo personInfo = new PersonInfo();        personInfo.setAge(40);        personInfo.setName("曹操");        Integer ret = blogXMLMapper.addPersonInfo(personInfo);        log.info("添加成功,影响行数:{},返回的主键值:{}",ret.toString(),personInfo.getId());//添加成功,影响行数:1,返回的主键值:7    }}    <insert id="addPersonInfo" useGeneratedKeys="true" keyProperty="id">        insert into blog        <trim prefix="(" suffix=")" suffixOverrides=",">            <if test="id != null">                id,            </if>            <if test="name != null">                name,            </if>            <if test="age != null">                age,            </if>        </trim>        values        <trim prefix="(" suffix=")" suffixOverrides=",">            <if test="id != null">                #{id},            </if>            <if test="name != null">                #{name},            </if>            <if test="age != null">                #{age},            </if>        </trim>    </insert>2.7 预编译/即时SQL预编译SQL(Prepared Statements):SQL语句在程序运行前被预先编译并存储在数据库中。执行时只需传递参数,无需重新编译SQL语句安全性高:通过参数化查询避免SQL注入攻击。参数化查询是一种将SQL语句与用户输入数据分离的数据库操作方式,查询语句中使用占位符(如?、@param等)代替直接拼接用户输入,执行时通过预编译机制将参数动态绑定到占位符位置性能优化:编译一次,多次执行,减少数据库开销即时SQL(Dynamic SQL):在程序运行时动态生成并立即编译执行,每次执行都可能涉及完整的SQL解析和编译过程灵活性高:可根据运行时条件动态拼接SQL语句潜在风险:直接拼接用户输入可能导致SQL注入性能开销:每次执行需重新编译#占位符会使用预编译机制,将参数值安全地绑定到SQL语句中,防止SQL注入攻击。MyBatis会将#替换为?,然后通过JDBC的预编译功能设置参数值$占位符直接进行字符串替换,将参数值拼接到SQL语句中,不会进行预编译或转义处理SQL注入攻击:当恶意输入" 'or 1 = '1 "时1.预编译SQL@Select("select * from blog where name= #{name}")List<UserInfo> queryByName(String name)预编译SQL会根据参数的类型判断是否需要加引号,上述name参数是String类型,需要加引号,这就是参数化查询的作用。最终SQL:select * from blog where name = " 'or 1='1 "; # 整个 'or 1='1 会作为name的值2.即时SQL@Select("select * from blog where name= '${name}'")List<UserInfo> queryByName(String name)即时SQL不会判断参数类型从而是否添加引号,所以需要手动加上单引号。最终SQL:select * from blog where name = ''or 1 = '1'; # 因为1=1是恒等式,所以该表的数据会被全部查————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/2401_89167985/article/details/152804543
  • [技术干货] JAVA中的int和Integer的区别和联系
    在 Java 中,int 和 Integer 是处理整数时最常用的两种类型,但二者在本质、用途、特性上有根本区别,核心差异可从以下维度清晰区分:一、本质区别:基本数据类型 vs 引用数据类型类型归属类别存储方式核心特点int基本数据类型直接存储数值(栈内存)不包含额外方法,仅存原始值Integer引用数据类型(类)存储对象引用(堆内存存对象,栈内存存地址)是 int 的包装类,包含工具方法简单理解:int 是 “裸数值”,Integer 是 “装着数值的盒子”—— 盒子里除了数值,还附带了操作数值的工具。二、核心差异对比1. 默认值不同int 作为基本类型,默认值为 0(如类中定义 private int num;,未赋值时 num 自动为 0);Integer 作为引用类型,默认值为 null(如 private Integer num;,未赋值时 num 为 null)。 ⚠️ 注意:若直接调用 Integer 变量的方法(如 num.toString())且未赋值,会触发 NullPointerException,而 int 不会。2. 适用场景不同int:适用于简单的数值计算、存储基本整数(如循环变量、年龄、分数等),性能更优(无需创建对象,节省内存);Integer:适用于需要对象特性的场景,例如:集合框架(List<Integer>、Map<String, Integer> 等,集合仅支持引用类型);泛型(<T> 类型参数必须是引用类型,不能是 int);需使用工具方法时(如数值转字符串 Integer.toString(num)、字符串转整数 Integer.parseInt("123"))。3. 特性差异:缓存机制Integer 有缓存池机制(JDK 1.5+),默认缓存 -128 ~ 127 范围内的整数对象,重复使用该范围的 Integer 时,不会创建新对象;而 int 无缓存,每次使用都是独立的数值。示例验证:java 运行    // Integer 缓存机制Integer a = 127; // 从缓存池获取对象Integer b = 127; // 复用同一缓存对象System.out.println(a == b); // true(引用地址相同)Integer c = 128; // 超出缓存范围,创建新对象Integer d = 128; // 新对象System.out.println(c == d); // false(引用地址不同)// int 无缓存,直接比较数值int e = 128;int f = 128;System.out.println(e == f); // true(数值相同) 4. 比较方式不同int 比较:用 == 直接比较数值(正确,因为存储的是原始值);Integer 比较:== 比较的是对象引用地址(仅在缓存范围内的整数会相等,超出范围则不等,易出错);正确比较方式:用 equals() 方法(比较数值),或先拆箱为 int 再用 ==。示例:java 运行    Integer x = 100;Integer y = new Integer(100); // 强制创建新对象(不走缓存)System.out.println(x == y); // false(引用地址不同)System.out.println(x.equals(y)); // true(数值相同)System.out.println((int)x == y); // true(拆箱后比较数值) 三、自动拆箱与装箱(JDK 1.5+)为简化 int 和 Integer 的转换,Java 提供自动拆箱(Unboxing) 和 自动装箱(Boxing):自动装箱:int 自动转为 Integer(如 Integer num = 10;,等价于 Integer num = Integer.valueOf(10););自动拆箱:Integer 自动转为 int(如 int val = num;,等价于 int val = num.intValue();)。注意:频繁拆装箱可能影响性能(如循环中大量转换),需避免不必要的操作。四、总结:如何选择?若仅需存储 / 计算基本整数,优先用 int(性能好、无空指针风险);若需集合、泛型、工具方法,或可能存储 null(如数据库字段允许为空),用 Integer;Integer 比较时务必用 equals(),避免用 == 踩缓存范围的坑。
  • [技术干货] java中使用正则表达式
    在Java中,正则表达式(Regular Expression)通过 java.util.regex 包实现,主要用于字符串匹配、分割、查找和替换。以下是详细的使用指南和示例:1. 核心类介绍Pattern:编译正则表达式,生成匹配模式。Matcher:执行匹配操作,提供查找、替换等方法。String 类方法:如 matches()、replaceAll() 等内置正则支持。2. 基本用法(1) 字符串匹配import java.util.regex.Pattern; import java.util.regex.Matcher; public class RegexExample { public static void main(String[] args) { String input = "Java is fun"; String regex = "Java"; // 方法1:String.matches()(完全匹配) boolean isMatch = input.matches(regex); // false(需完全匹配"Java is fun") System.out.println(isMatch); // 方法2:Pattern + Matcher(部分匹配) Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(input); boolean found = matcher.find(); // true(找到"Java") System.out.println(found); } } (2) 提取匹配内容String text = "Email: user@example.com, Backup: backup@test.org"; Pattern emailPattern = Pattern.compile("\\b[\\w.-]+@[\\w.-]+\\b"); Matcher emailMatcher = emailPattern.matcher(text); while (emailMatcher.find()) { System.out.println("Found email: " + emailMatcher.group()); } // 输出: // Found email: user@example.com // Found email: backup@test.org (3) 字符串分割String csv = "apple,banana,orange"; String[] fruits = csv.split(","); // 按逗号分割 for (String fruit : fruits) { System.out.println(fruit); } (4) 字符串替换String phone = "123-456-7890"; String formatted = phone.replaceAll("(\\d{3})-(\\d{3})-(\\d{4})", "($1) $2-$3"); System.out.println(formatted); // 输出:(123) 456-7890 3. 常用正则语法符号含义示例.任意字符(除换行符)a.c → “abc”, “a1c”\\d数字\\d{3} → “123”\\w字母、数字、下划线\\w+ → “Java_123”+1次或多次go+l → “gol”, “goool”*0次或多次ab*c → “ac”, “abbc”?0次或1次colou?r → “color”{n,m}n到m次\\d{2,4} → “12”, “123”[abc]a、b或c[a-z]+ → “hello”^字符串开头^Hello → “Hello…”$字符串结尾world$ → “…world”``或\\s空白字符(空格、制表符等)\\s+ → " \t\n"\\b单词边界\\bcat\\b → “cat”4. 高级用法(1) 分组捕获String date = "2023-10-05"; Pattern datePattern = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})"); Matcher dateMatcher = datePattern.matcher(date); if (dateMatcher.matches()) { String year = dateMatcher.group(1); // "2023" String month = dateMatcher.group(2); // "10" String day = dateMatcher.group(3); // "05" System.out.println(year + "-" + month + "-" + day); } (2) 非贪婪匹配String html = "<div>Hello</div><div>World</div>"; Pattern greedyPattern = Pattern.compile("<div>.*</div>"); // 贪婪匹配 Pattern lazyPattern = Pattern.compile("<div>.*?</div>"); // 非贪婪匹配 Matcher greedyMatcher = greedyPattern.matcher(html); greedyMatcher.find(); // 匹配整个"<div>Hello</div><div>World</div>" Matcher lazyMatcher = lazyPattern.matcher(html); lazyMatcher.find(); // 仅匹配第一个"<div>Hello</div>" (3) 预定义字符类String text = "Price: $19.99, Discount: 10%"; Pattern numberPattern = Pattern.compile("\\$\\d+\\.\\d{2}"); // 匹配价格 Matcher numberMatcher = numberPattern.matcher(text); numberMatcher.find(); // 匹配"$19.99" 5. 性能优化预编译 Pattern:重复使用时,避免每次创建 Pattern 对象。private static final Pattern EMAIL_PATTERN = Pattern.compile("\\b[\\w.-]+@[\\w.-]+\\b"); public boolean isValidEmail(String email) { return EMAIL_PATTERN.matcher(email).matches(); } 避免过度回溯:复杂正则可能导致性能问题(如 (a+)+ 匹配 "aaaaaaaaaaaaaX")。6. 常见正则示例需求正则表达式验证邮箱^[\\w.-]+@[\\w.-]+\\.[a-z]{2,}$提取URL参数[?&](\\w+)=([^&#]+)匹配手机号(中国)^1[3-9]\\d{9}$去除多余空格\\s+ → 替换为 " "验证密码强度(大小写+数字)^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{8,}$7. 工具推荐在线测试工具:Regex101、RegExrIDE插件:IntelliJ IDEA的正则表达式测试器(Ctrl+Alt+F7)。总结简单匹配:直接使用 String.matches() 或 String.split()。复杂操作:使用 Pattern + Matcher 组合。性能关键场景:预编译 Pattern 并复用。调试技巧:通过 Matcher.group() 和在线工具逐步验证正则逻辑。通过合理使用正则表达式,可以高效处理文本验证、解析和转换任务。
  • [技术干货] Java代码中使用接口的好处
    在Java中,接口(Interface)是一种核心的抽象机制,它通过定义契约(方法签名和常量)来规范类的行为。接口的使用带来了多重好处,尤其在大型项目、团队协作和系统扩展中具有关键作用。以下是Java中使用接口的主要优势及具体场景说明:1. 解耦与依赖倒置(DIP)核心思想:接口将**调用方(客户端)与实现方(具体类)**解耦,使两者仅依赖抽象而非具体实现。示例:// 定义接口 interface PaymentService { void pay(double amount); } // 具体实现1 class CreditCardPayment implements PaymentService { public void pay(double amount) { System.out.println("Paid " + amount + " via Credit Card"); } } // 具体实现2 class PayPalPayment implements PaymentService { public void pay(double amount) { System.out.println("Paid " + amount + " via PayPal"); } } // 客户端代码(依赖接口而非具体类) public class PaymentProcessor { private PaymentService paymentService; public PaymentProcessor(PaymentService service) { this.paymentService = service; } public void processPayment(double amount) { paymentService.pay(amount); // 调用接口方法 } } 优势:客户端只需知道接口方法,无需关心具体支付方式(如信用卡、PayPal)的实现细节。切换支付方式时,只需修改PaymentProcessor的构造函数参数,无需改动内部逻辑。2. 多态与灵活扩展核心思想:一个类可实现多个接口,或同一接口可被多个类实现,实现运行时动态绑定。示例:interface Loggable { void log(String message); } class FileLogger implements Loggable { public void log(String message) { System.out.println("Logging to file: " + message); } } class DatabaseLogger implements Loggable { public void log(String message) { System.out.println("Logging to database: " + message); } } // 根据配置动态选择日志方式 public class App { public static void main(String[] args) { Loggable logger; if (args[0].equals("file")) { logger = new FileLogger(); } else { logger = new DatabaseLogger(); } logger.log("System started"); // 多态调用 } } 优势:新增日志方式(如网络日志)时,只需新增一个实现类,无需修改现有代码。符合开闭原则(OCP):对扩展开放,对修改关闭。3. 定义契约与强制规范核心思想:接口明确规定了类必须实现的方法,确保对象行为的一致性。示例:interface Comparable<T> { int compareTo(T other); } class Person implements Comparable<Person> { private String name; private int age; public int compareTo(Person other) { return this.age - other.age; // 强制实现比较逻辑 } } 优势:任何实现Comparable的类必须提供compareTo方法,否则编译失败。框架(如Java集合类)可依赖接口统一处理对象(如排序、查找)。4. 模拟多继承(Java单继承的补充)核心思想:Java类只能单继承,但可通过实现多个接口间接实现多继承的效果。示例:interface Flyable { void fly(); } interface Swimmable { void swim(); } class Duck implements Flyable, Swimmable { public void fly() { System.out.println("Duck is flying"); } public void swim() { System.out.println("Duck is swimming"); } } 优势:避免类继承导致的钻石问题(Diamond Problem)。灵活组合多个行为(如飞行+游泳)。5. 接口与默认方法(Java 8+)核心思想:接口可通过default关键字提供默认实现,减少重复代码。示例:interface Listener { void onEvent(); // 抽象方法 default void logEvent() { // 默认方法 System.out.println("Event occurred"); } } class ButtonListener implements Listener { public void onEvent() { System.out.println("Button clicked"); } // 无需实现logEvent(),可直接使用默认实现 } 优势:向旧接口添加方法时,无需强制所有实现类修改代码。共享通用逻辑(如日志、监控)。6. 接口与函数式编程(Java 8+)核心思想:接口(如Runnable、Function)是函数式编程的基础,支持Lambda表达式。示例:// 传统匿名类 Runnable task1 = new Runnable() { public void run() { System.out.println("Task 1"); } }; // 使用Lambda(接口简化) Runnable task2 = () -> System.out.println("Task 2"); 优势:代码更简洁,适合事件处理、并发编程等场景。结合Stream API实现声明式数据处理。7. 接口与测试驱动开发(TDD)核心思想:接口便于编写测试替身(Mock/Stub),隔离依赖。示例:// 真实依赖 interface UserRepository { User findById(int id); } // 测试时使用Mock class MockUserRepository implements UserRepository { public User findById(int id) { return new User(1, "Test User"); // 返回固定数据 } } // 测试类 public class UserServiceTest { @Test public void testGetUser() { UserRepository mockRepo = new MockUserRepository(); UserService service = new UserService(mockRepo); User user = service.getUser(1); assertEquals("Test User", user.getName()); } } 优势:无需启动数据库或外部服务,加快测试速度。明确测试边界,聚焦被测逻辑。总结:何时使用接口?场景接口的优势需要解耦模块客户端依赖抽象,实现可替换预期未来行为会扩展新增实现类不影响现有代码定义框架或库的规范强制用户实现特定方法(如Servlet、JPA Entity)实现多行为组合类通过接口组合飞行、游泳等能力需要函数式编程支持使用Lambda表达式简化代码(如Predicate、Consumer)编写可测试的代码通过Mock隔离依赖,提高测试覆盖率最佳实践:优先使用接口定义行为,而非抽象类(除非需要共享状态或复杂初始化逻辑)。接口命名应体现行为(如Runnable、Serializable),而非具体类(避免UserInterface)。结合设计模式(如策略模式、工厂模式)充分发挥接口的灵活性。
  • [技术干货] Java中依赖管理工具对比与性能优化(Maven vs Gradle)【转载】
    一、Java依赖管理的核心挑战:从“版本地狱”到“依赖爆炸”1.1 依赖管理的三大痛点版本冲突:项目中多个依赖库引用同一组件的不同版本(如Log4j 1.2.17 vs Log4j 1.2.18)。后果:运行时异常(如NoSuchMethodError)、日志框架绑定冲突。依赖膨胀:项目依赖树层级过深,导致构建时间延长、JAR包臃肿。案例:某电商系统依赖树包含12,000+ JAR包,构建耗时45分钟。隐性依赖传递:依赖库的间接依赖未被显式声明,导致环境一致性问题。1.2 Maven vs Gradle:谁更适合依赖管理维度MavenGradle依赖声明XML配置(pom.xml)DSL配置(build.gradle)版本冲突解决路径优先(最近依赖优先)灵活策略(resolutionStrategy)构建速度依赖缓存慢支持增量构建、并行任务学习成本低(XML语法)高(Groovy/Kotlin DSL)企业适配传统项目主流微服务/云原生项目主流二、Java依赖管理的5大黄金法则2.1 法则一:统一依赖版本(BOM管理)核心思想:通过Bill of Materials(BOM)统一依赖版本,避免版本碎片化。Maven实践:123456789101112<!-- 定义BOM文件 --><dependencyManagement>    <dependencies>        <dependency>            <groupId>com.example</groupId>            <artifactId>common-bom</artifactId>            <version>1.0.0</version>            <scope>import</scope>            <type>pom</type>        </dependency>    </dependencies></dependencyManagement>Gradle实践:12345// 使用平台依赖(Platform)dependencies {    implementation platform('com.example:common-bom:1.0.0')    implementation 'org.springframework.boot:spring-boot-starter-web'}2.2 法则二:依赖排除与强制版本Maven排除依赖:1234567891011<dependency>    <groupId>com.example</groupId>    <artifactId>example-dependency</artifactId>    <version>1.0.0</version>    <exclusions>        <exclusion>            <groupId>org.conflicting</groupId>            <artifactId>conflicting-library</artifactId>        </exclusion>    </exclusions></dependency>Gradle强制版本:12345configurations.all {    resolutionStrategy {        force 'org.springframework:spring-core:5.3.20'    }}2.3 法则三:依赖声明顺序的艺术推荐顺序:本模块子模块依赖(如example-dao、example-service)通用基础组件(Spring、Jackson、Log4j)公司内部框架(RPC、Redis客户端)特定业务依赖测试依赖(JUnit、Mockito)示例:123456789101112131415161718192021<dependencies>    <!-- 子模块依赖 -->    <dependency>        <groupId>com.example</groupId>        <artifactId>example-dao</artifactId>        <version>1.0.0</version>    </dependency>    <!-- 通用组件 -->    <dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-core</artifactId>        <version>5.3.20</version>    </dependency>    <!-- 测试依赖 -->    <dependency>        <groupId>junit</groupId>        <artifactId>junit</artifactId>        <version>4.13.2</version>        <scope>test</scope>    </dependency></dependencies>2.4 法则四:依赖树分析与可视化Maven命令:1mvn dependency:tree > dependency-tree.txtGradle命令:1gradle dependencies --configuration runtimeClasspath工具推荐:Maven Dependency Plugin:分析冗余依赖。Gradle Dependency Report:生成可视化依赖图。2.5 法则五:依赖注入与模块化设计Spring依赖注入示例:123456789@Componentpublic class MyService {    private final MyDependency dependency;     @Autowired    public MyService(MyDependency dependency) {        this.dependency = dependency;    }}模块化设计:将公共依赖定义在父POM中,减少重复声明。使用<optional>标记可选依赖,避免传递性污染。三、3个实战案例:从崩溃到高效3.1 案例一:某金融系统依赖冲突修复问题:项目依赖Spring Boot 2.5.5和Hadoop 3.3.0,导致Guava版本冲突(Spring Boot依赖Guava 26.0,Hadoop依赖Guava 31.0)。解决方案:123456<!-- 强制使用Guava 31.0 --><dependency>    <groupId>com.google.guava</groupId>    <artifactId>guava</artifactId>    <version>31.1-jre</version></dependency>效果:构建时间从30分钟降至6秒,运行时异常消失。3.2 案例二:某电商系统的依赖优化问题:依赖树包含12,000+ JAR包,构建耗时45分钟。解决方案:使用mvn dependency:analyze分析冗余依赖。排除无用依赖(如jackson-databind的多余模块)。启用Gradle的--parallel和--build-cache。效果:依赖数量减少60%,构建时间降至12分钟。3.3 案例三:微服务项目的BOM统一管理问题:100+微服务项目依赖版本不一致(如Spring Boot 2.4.0 vs 2.5.5)。解决方案:创建公司级BOM文件company-bom:1234567891011<dependencyManagement>    <dependencies>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-parent</artifactId>            <version>2.5.5</version>            <scope>import</scope>            <type>pom</type>        </dependency>    </dependencies></dependencyManagement>所有微服务继承company-bom。效果:版本一致性提升100%,升级成本降低90%。四、10个避坑指南:Java依赖管理的“致命陷阱”版本冲突陷阱:使用mvn dependency:tree或gradle dependencies定期检查依赖树。隐性依赖传递陷阱:标记非必需依赖为<optional>true</optional>。依赖膨胀陷阱:使用mvn dependency:purge-local-repository清理本地仓库冗余文件。测试依赖污染生产环境:确保测试依赖(<scope>test</scope>)不被意外打包到生产JAR中。Maven默认仓库陷阱:自定义仓库优先级,避免拉取恶意依赖:1234567<repositories>    <repository>        <id>company-nexus</id>        <url>https://nexus.company.com/repository/maven-public/</url>        <releases><enabled>true</enabled></releases>    </repository></repositories>Gradle插件冲突陷阱:使用gradle properties显式指定插件版本。多模块项目依赖顺序陷阱:在父POM中定义子模块依赖顺序,避免构建失败。依赖范围混淆陷阱:区分compileOnly、runtimeOnly、provided等依赖范围。依赖版本“最新”陷阱:避免使用LATEST或RELEASE版本,固定版本号以确保稳定性。CI/CD环境依赖一致性陷阱:在CI/CD流水线中强制拉取依赖,避免本地缓存差异。五、未来趋势:AI驱动的依赖管理革命5.1 AI辅助依赖分析GitHub Copilot生成依赖配置:12345// Copilot生成的Gradle依赖声明dependencies {    implementation 'org.springframework.boot:spring-boot-starter-web:3.0.0'    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'}5.2 云原生依赖管理Nexus Repository Manager 3.0:支持实时依赖分析、安全漏洞扫描。Azure Artifactory集成:自动同步Maven Central依赖,减少网络延迟。5.3 量子计算与依赖优化量子算法解决依赖冲突:理论上可通过量子叠加态同时计算多个依赖版本的兼容性。六、你的问题答案在这里!问题1:Java依赖管理真的能彻底解决版本冲突吗?答案:通过BOM统一管理、强制版本、依赖排除等策略,可大幅减少冲突,但无法完全避免。问题2:Maven和Gradle哪个更适合大型项目?答案:Gradle更适合复杂项目(支持增量构建、DSL灵活),Maven更适合传统项目(学习成本低)。问题3:如何快速定位依赖冲突?答案:使用mvn dependency:tree或gradle dependencies,结合exclude排除冲突依赖。七、结语:依赖管理的“黄金法则”“没有银弹,只有适合的策略!”Maven:适合传统项目,稳定性强。Gradle:适合复杂项目,灵活性高。未来方向:AI+云原生驱动的“全自动依赖管理”。
  • [技术干货] Bun替代Nodejs,JavaScrpit运行新环境-Bun,更快、更现代的开发体验
    odejs我想很多人在使用,已经得到广泛运用。但今天介绍一款比node.js高阶的一个新组件Bun,它在HTTP服务器性能、文件系统操作、启动时间、包安装时间性能上高于node.js。什么是bun,Bun的设计理念是开箱即用,减少配置和依赖,让开发者可以更专注于编写代码。Bun是一个全新的JavaScript运行时和工具链,它的核心目标是替代Node.js,提供更快的性能、更简洁的API和更好的开发体验。Bun使用JavaScriptCore引擎(也是Safari浏览器使用的引擎),V8引擎是Node.js使用的引擎,这是其性能优势的主要来源之一。Bun不仅是一个运行时,它还集成了包管理器、打包工具、测试运行器等功能,目标成为一站式的JavaScript开发平台。我这里重点对这两位前端的主角在性能、内置功能、环境、兼容性、nodejs项目迁移、bun的适用场景进行对比总结。性能优势启动速度更快Bun的底层做了大量的优化,启动速度比Node.js快10-20倍。这主要是因为Bun使用了JavaScriptCore引擎,特别是在微服务和serverless环境中,由于快速启动尤其重要,bun就可以明显缩短冷启动的时间了。// 启动时间对比// Node.js: ~50ms vs Bun: ~5msAI运行代码javascript包管理器更快bun有自己内置的包管理器,它和npm、yarn比较,通过bun install速度要快5-10倍。因为它使用了Zig来编写,它依赖解析算法很高效、而且还使用了高效的缓存策略,可直接与文件系统交互,并行下载安装依赖。# npm安装: ~15秒 vs bun安装: ~2秒AI运行代码bash运行时性能更好bun对比常见的操作也比node.js要快。比如文件系统的操作快2-5倍,http请求速度快2倍,json解析快2倍,流处理场景约快4倍。内置功能原生TypeScript支持它内置了TypeScript支持,无需编译可直接运行ts文件,极大的简化了开发流程。// Node.js需要: tsc user.ts && node user.js// Bun只需: bun user.tsAI运行代码typescript内置测试框架内置了测试框架,语法和Jest相似,但速度应该要快3-5倍(消除了安装和配置单独测试库的需要)。// 测试示例import { expect, test } from "bun:test";test("加法运算", () => {  expect(2 + 2).toBe(4);});AI运行代码javascript内置打包工具打包包含了一个高性能的javascript打包器,则可以替代掉webpack、Rollup或esbuild等工具,并支持代码分割、压缩等。// 打包示例await Bun.build({  entrypoints: ['./src/index.js'],  outdir: './dist',  minify: true});AI运行代码javascript文件操作更简单文件操作相关的API更为友好、简洁和直观,极大简化了常见的文件操作任务。// Bun文件操作const text = await Bun.file("data.txt").text();const config = await Bun.file("config.json").json();await Bun.write("output.txt", "Hello Bun!");AI运行代码javascript我们对比Node.js的方式:// Node.js文件操作const text = await fs.readFile("data.txt", "utf8");const json = JSON.parse(await fs.readFile("config.json", "utf8"));await fs.writeFile("output.txt", "Hello Node!");AI运行代码javascriptHTTP服务器高性能的HTTP服务器API,它比Node.js的http模块或Express框架更简单,同时性能更好,更为标准化Web API(如URL、Response)。// Bun HTTP服务器Bun.serve({  port: 3000,  fetch(req) {    const url = new URL(req.url);    if (url.pathname === "/") {      return new Response("欢迎访问首页");    }    return new Response("404 Not Found", { status: 404 });  },});AI运行代码javascript对比Node.js + Express的实现:// Node.js + Expressconst app = express();app.get('/', (req, res) => {  res.send('欢迎访问首页');});app.listen(3000);AI运行代码javascript环境变量和配置简化了环境变量的管理,Bun自动加载项目根目录中的.env文件,无需安装dotenv等第三方库。// .env文件DATABASE_URL=postgresql://localhost:5432/mydb// 在代码中直接使用console.log(process.env.DATABASE_URL);AI运行代码javascript脚本工具Bun很适合编写高性能的脚本工具,特别是需要处理大量文件或数据的场景。// 批量处理文件示例const files = await Array.fromAsync(new Bun.Glob("**/*.md").scan());for (const filePath of files) {  const content = await Bun.file(filePath).text();  await Bun.write(filePath, content.replace(/TODO/g, "待办事项"));}AI运行代码javascriptAPI服务器Bun的高性能HTTP服务器和内置SQLite支持使其成为构建API服务的理想选择。// SQLite集成示例import { Database } from 'bun:sqlite';const db = new Database('data.sqlite');db.exec(`CREATE TABLE IF NOT EXISTS todos (id INTEGER PRIMARY KEY, title TEXT)`);// API服务器Bun.serve({  port: 3000,  async fetch(req) {    if (req.url.endsWith('/api/todos')) {      const todos = db.prepare('SELECT * FROM todos').all();      return Response.json(todos);    }    return new Response('Not Found', { status: 404 });  }});AI运行代码javascript兼容性Bun设计为与Node.js高度兼容,可以运行大多数Node.js代码和npm包。但仍有一些区别和限制:兼容的功能兼容绝大多数场景,比如CommonJS和ES模块系统、核心模块(fs, path, http等)、npm生态系统中的大多数包、process全局对象、Buffer API、EventEmitter、大多数Node.js标准库API。不完全兼容的功能某些原生模块可能需要重新编译一些依赖Node.js内部API的库可能不工作某些流API的实现有差异某些特定于Node.js的功能(如cluster模块)可能有不同行为从Node.js迁移到Bun如果你正在考虑从Node.js迁移到Bun,以下是一些建议和注意事项:迁移步骤安装Bun:curl -fsSL https://bun.sh/install | bash测试兼容性:在不修改代码的情况下,尝试用Bun运行你的应用替换包管理器:将npm/yarn命令替换为bun命令利用Bun特性:逐步采用Bun特有的API和功能更新测试:考虑迁移到Bun的内置测试框架何时选择Bun适合Bun的场景开发环境和工具链:利用Bun的快速启动时间和集成工具API和微服务:利用Bun的高性能HTTP服务器脚本和自动化:利用Bun的快速启动时间和文件操作性能,做些零活儿效率特别高新项目:从头开始的项目可以充分利用Bun的现代API和性能,从一开始就发挥它的全部优势性能关键应用:需要最大化性能的应用,需要速度和性能的场合可能不适合Bun的场景大型企业应用:需要稳定性、成熟性较高的关键业务依赖特定Node.js功能:不完全支持的Node.js特性的应用,而Bun不完全支持需要广泛部署选项:需要跨平台,在各种环境中部署的应用依赖大量原生模块:使用许多需要重新编译的C++扩展的应用Bun如同一股清流,为JavaScript运行时领域带来了新的活力。它以现代化的设计和惊人的性能,让开发体验焕然一新。虽然它还在成长的路上,不能立刻满足所有生产需求,但它的变化和创新精神,已经让它成为技术圈的新星。如果你要开启一个新项目,尤其是追求极致性能和开发体验的,Bun绝对值得一试,让我们的编程之路更加轻松愉快。如果本文能给你提供启发和帮助,还请留下你的一健三连(点赞、转发、评论),给我一些鼓励,谢谢。————————————————原文链接:https://blog.csdn.net/m0_57874805/article/details/150647891
  • [技术干货] Java霸主未逝:不可撼动的生态与新特性的革命潜力
    Java霸主未逝:不可撼动的生态与新特性的革命潜力引言:在编程语言的巨变时代重新审视Java在技术飞速演进的时代,编程语言的世界仿佛一片汹涌的海洋,每天都有新的语言和框架涌现,声称要颠覆现有秩序。从Python在数据科学和人工智能领域的崛起,到Go语言在并发处理和高性能网络服务中的优异表现,再到Rust在系统编程和安全关键型应用中的强势进攻,似乎每一种语言都在挑战Java这个长期占据TIOBE指数前三的"老牌王者"。不少初学者和技术爱好者开始质疑:在这个充满变数的时代,Java是否已经失去了往日的光彩?它那长达二十多年的霸主地位是否正在被动摇?更重要的是,对于今天的开发者来说,投入时间学习Java还是一项值得的投资吗?事实上,尽管新语言层出不穷,Java依然在全球企业级应用开发中占据着绝对主导地位。根据2023年的最新数据,全球仍有超过90%的企业使用Java作为主要开发语言,包括金融、电信、电商等关键行业。更有趣的是,Java并没有像某些预言中那样逐渐老去,反而通过持续的创新和生态扩展,不断巩固和扩大着自己的领土。从Android移动开发到大数据处理框架,从微服务架构到云原生应用,Java的身影无处不在。甚至在新兴的人工智能和物联网领域,Java也通过不断完善的特性和性能优化,找到了自己的立足点。本文将从多个维度深入分析Java的现状和未来,客观评估其核心地位是否真的遭到其他语言的撼动,详细探讨Java生态系统中那些难以被替代的核心优势,并重点分析Project Loom的虚拟线程、Project Panama的原生镜像等新特性如何为Java注入新的活力。无论你是Java的忠实拥护者,还是对Java未来持怀疑态度的观察者,抑或是正在选择技术方向的初学者,这篇文章都将为你提供一个全面而深入的视角,帮助你在技术的浪潮中做出更明智的决策。第一章 Java的现状:数字背后的真相1.1 市场占有率与行业渗透Java自1995年问世以来,已经走过了近30个年头。在技术领域,这几乎是一个永恒的时间跨度,足以让大多数技术从诞生、繁荣走向衰落。然而Java却打破了这个规律,依然保持着惊人的活力。根据TIOBE编程社区指数的最新统计,Java在2023年依然稳居前三甲,与Python和C语言共同占据着主导地位。更令人惊讶的是,这一排名在过去的20多年间几乎没有发生过实质性变化。深入行业内部,Java的统治地位更加明显。在财富500强企业中,有超过90%的公司将Java作为主要的企业级应用开发语言。金融行业几乎完全建立在Java生态系统之上——全球最大的投资银行、商业银行和保险公司都依赖Java构建其核心交易系统、风险管理系统和客户服务平台。电信行业同样如此,从基站控制器到计费系统,Java无处不在。甚至在传统的制造业和零售业,Java也扮演着关键角色,支撑着供应链管理、库存控制和电子商务平台。Android平台的兴起进一步扩展了Java的疆域。虽然近年来Kotlin在Android开发中获得了官方支持并逐渐流行,但事实是绝大多数现有的Android应用仍然基于Java构建,而且新的Java代码仍在不断被添加到这些应用中。据统计,Google Play商店中超过70%的应用主要使用Java开发,这个数字在企业级应用中甚至更高。1.2 就业市场与开发者社区从就业市场的角度观察,Java开发者的需求持续旺盛。根据Indeed和LinkedIn等招聘平台的数据,Java开发岗位的数量长期位居所有编程语言中的前三位,且平均薪资水平高于许多其他语言的开发者。更重要的是,Java开发者的就业领域极为广泛,从初创公司到科技巨头,从互联网行业到传统制造业,都需要Java人才。Java开发者社区的规模和活跃度也是其生命力的重要指标。Stack Overflow的年度开发者调查显示,Java一直是使用最广泛的语言之一,拥有数百万活跃开发者。GitHub上的Java项目数量超过100万,且持续增长。这种规模的社区意味着任何Java开发者都能轻松找到学习资源、开源库和技术支持,极大降低了学习和开发成本。JVM(Java虚拟机)语言家族的出现进一步丰富了Java生态系统。虽然Scala、Kotlin和Groovy等语言在某些特性上超越了Java本身,但它们都运行在JVM之上,共享Java庞大的库和工具生态系统。这种现象实际上强化而非削弱了Java的地位,因为它使JVM成为了更多开发者的选择,即使他们不直接使用Java语言。第二章 Java生态系统的不可替代性2.1 JVM:工程学的奇迹Java虚拟机构成了Java生态系统的基石,也是Java最难以被替代的核心优势。JVM是一个工程学上的奇迹,它通过"一次编写,到处运行"的理念,解决了跨平台兼容性这一长期困扰软件开发者的难题。经过二十多年的持续优化,现代JVM已经成为一个极其复杂和精密的运行环境,提供了许多独特而强大的特性。即时编译技术是JVM最引人注目的特性之一。HotSpot JVM通过解释执行和即时编译的结合,实现了近乎原生代码的性能,同时又保持了跨平台优势。它采用自适应优化技术,监控代码执行情况,识别热点代码并将其编译为高度优化的机器码。这种运行时常量优化甚至比静态编译的语言更具优势,因为JVM可以根据实际运行情况做出更智能的优化决策。垃圾回收机制是JVM的另一大亮点。从最初的串行收集器到现在的G1、ZGC和Shenandoah等先进收集器,Java的垃圾回收技术已经发展到令人惊叹的水平。现代垃圾收集器可以实现毫秒级的暂停时间,即使处理数TB规模的堆内存也能保持稳定性能。这对于需要高可用性和低延迟的企业级应用至关重要。JVM的监控和诊断能力同样无与伦比。Java管理扩展提供了丰富的监控接口,VisualVM、JConsole等工具可以深入洞察应用运行状态。Java飞行记录器和JDK任务控制系统提供了生产环境下的低开销性能分析能力。这些工具和特性使得Java应用在复杂企业环境中的调试和优化变得更加可行。2.2 企业级框架与库的成熟度Java拥有世界上最成熟和完善的企业级开发生态系统。Spring框架已经成为事实上的Java企业开发标准,提供了从依赖注入到Web开发,从数据访问到安全认证的全方位解决方案。Spring Boot进一步简化了Java应用的初始化和部署过程,使开发者能够快速创建独立运行的生产级应用。Java持久化API和Hibernate框架解决了对象-关系映射这一复杂问题,大大简化了数据库操作。Jakarta EE(前身为Java EE)提供了一套标准化的企业级API,得到了所有主要应用服务器厂商的支持。这些经过时间检验的框架和库为企业级应用开发提供了可靠的基础。微服务架构的兴起进一步证明了Java生态系统的适应性。Spring Cloud、Micronaut和Quarkus等框架为构建云原生Java应用提供了完整解决方案,支持服务发现、配置管理、熔断器等微服务模式。Netflix、Twitter和Amazon等大型互联网公司都使用Java构建其微服务架构,处理每天数十亿次的请求。2.3 安全性与企业支持在企业环境中,安全性往往是首要考虑因素。Java拥有经过严格测试和安全审计的安全模型,提供了细粒度的访问控制机制。Java安全架构包括字节码验证器、安全管理器和访问控制器等组件,构成了一个多层次的安全防御体系。Java长期支持版本为企业提供了稳定性保证。Oracle和OpenJDK社区都提供长期支持版本,保证关键安全更新和错误修复能够持续多年。这种可预测性和稳定性对于需要长期运行的企业应用至关重要。大型科技公司对Java的持续投入也确保了其未来发展。Oracle、IBM、Red Hat、Amazon和Microsoft等公司都雇佣了大量Java核心开发者,参与OpenJDK项目的开发。这种多厂商支持的模式避免了单一公司控制带来的风险,为Java的未来发展提供了坚实保障。第三章 新特性的革命性潜力3.1 Project Loom与虚拟线程Java最大的传统挑战之一是并发编程的复杂性。虽然Java很早就提供了强大的线程模型和并发工具包,但基于操作系统线程的实现限制了其扩展性。每个Java线程都对应一个操作系统线程,这意味着创建数千个线程就会消耗大量系统资源,导致性能下降。Project Loom旨在彻底改变这一现状,引入轻量级的虚拟线程。虚拟线程是由JVM管理的轻量级线程,与操作系统线程解耦。一个操作系统线程可以运行数千个虚拟线程,极大地提高了并发规模和效率。虚拟线程的使用方式与传统线程几乎完全相同,大大降低了学习成本。虚拟线程的革命性在于它使阻塞式编程模型重新变得可行。开发者可以编写简单直观的阻塞代码,而无需担心性能问题。JVM会在虚拟线程遇到阻塞操作时自动将其挂起,切换到其他虚拟线程,从而高效利用硬件资源。这种模型既保持了简单性,又获得了高性能。初步基准测试显示,虚拟线程能够轻松支持百万级别的并发连接,而内存占用仅为传统线程模型的几分之一。这对于需要处理大量并发请求的Web服务器、微服务和反应式应用具有重大意义。虚拟线程有可能使Java在高并发领域重新获得领先地位,甚至超越Go和Erlang等以并发能力著称的语言。3.2 GraalVM与原生镜像Java传统上因启动时间较长和内存占用较高而受到批评,这在容器化和无服务器计算时代显得尤为突出。GraalVM和原生镜像技术正是为了解决这些问题而诞生的。GraalVM是一个高性能的JDK发行版,采用了先进的即时编译技术,能够显著提高应用程序性能。更引人注目的是它的原生镜像功能,允许将Java应用程序提前编译为本地可执行文件。这些原生镜像具有极快的启动速度(通常只需几毫秒)和较低的内存占用,同时仍然保持了Java的类型安全和内存安全特性。Spring Boot、Micronaut和Quarkus等框架已经深度集成了原生镜像支持,使开发者能够轻松构建云原生Java应用。AWS Lambda等无服务器平台也加强了对Java原生镜像的支持,使Java成为函数即服务场景中的可行选择。原生镜像技术不仅解决了Java在云原生环境中的痛点,还开辟了新的应用领域。现在Java可以用于命令行工具、嵌入式系统和边缘计算场景,这些传统上由C++和Go主导的领域。这种扩展能力进一步巩固了Java的生态系统优势。3.3 Project Panama与外部函数接口Java通过JNI调用本地代码的机制一直以复杂和性能低下而闻名。Project Panama旨在革新Java与本地代码的交互方式,引入更简单、更高效的外部函数接口。新的外部函数API提供了类型安全的方式调用本地库,避免了JNI的复杂性和开销。Memory API提供了安全高效的内存访问方法,减少了内存泄漏和错误的风险。这些改进使Java能够更好地与本地库交互,同时保持Java的安全性和可移植性。Project Panama的重要意义在于它使Java能够更有效地利用硬件特性和专用库。现在Java程序可以轻松调用CUDA库进行GPU加速,或者使用TensorFlow和PyTorch进行机器学习推理。这为Java在人工智能和高性能计算领域的发展打开了新的大门。3.4 其他语言特性改进Java的持续更新带来了许多其他重要特性。记录类简化了不可变数据载体的定义,模式匹配简化了条件逻辑,密封类提供了更精确的类层次控制。这些特性使Java代码更加简洁、表达力更强,同时保持了类型安全性。Vector API引入了对向量计算的支持,允许开发者编写可移植的高性能向量化代码。这为科学计算、机器学习和多媒体处理等领域的应用提供了重要支持。这些语言改进表明Java并没有停滞不前,而是在保持向后兼容性的同时,不断吸收现代编程语言的最佳特性。Java正在变得更加简洁、表达力更强,同时保持了其核心优势和稳定性。第四章 Java面临的挑战与应对4.1 新兴语言的竞争尽管Java依然强大,但它确实面临着来自新兴语言的激烈竞争。Python在数据科学和机器学习领域的统治地位无可争议,Go语言在云原生基础设施和命令行工具中越来越受欢迎,Rust凭借其内存安全性和高性能在系统编程领域崭露头角。每种语言都有其特定的优势领域:Python拥有丰富的数据科学库和简洁的语法,Go提供了简单的并发模型和快速的编译时间,Rust则提供了无垃圾回收的内存安全保证。这些语言在特定场景下确实比Java更具优势。然而,重要的是认识到Java与这些语言之间的关系不完全是零和游戏。许多组织采用多语言策略,在不同场景中使用最适合的语言。Java的强项在于大型复杂企业应用的开发,这方面的优势仍然难以撼动。Java生态系统通过互操作性来应对多语言趋势。GraalVM支持多种语言运行在同一个运行时环境中,Spring框架提供了与其他语言服务的集成能力。这种开放性使Java能够在一个多语言世界中保持其核心地位。4.2 云原生时代的适应云原生计算对传统Java应用提出了新的挑战。容器环境强调轻量级和快速启动,无服务器平台需要极低的内存占用和瞬时扩展能力。Java传统上较长的启动时间和较高的内存占用在这些环境中处于劣势。Java社区已经积极应对这些挑战。通过模块化系统,开发者可以创建更小的运行时镜像,只包含应用程序实际需要的模块。框架如Quarkus和Micronaut专门为云原生环境设计,提供了极快的启动速度和低内存占用。工具链的改进也帮助Java更好地适应云原生时代。Jib等工具简化了Java应用容器化的过程,云原生构建包提供了自动化的应用容器化方案。这些工具降低了Java应用向云原生迁移的门槛。4.3 开发体验的现代化开发者体验是现代编程语言竞争的重要战场。Java传统上被认为繁琐而冗长,缺乏现代语言的简洁性和表达力。Java通过持续的语言改进来解决这些问题。var局部变量类型推断减少了样板代码,文本块简化了多行字符串的处理,记录类提供了简洁的数据载体定义方式。这些改进使Java代码更加简洁,同时保持了类型安全。开发工具也在不断进步。Visual Studio Code的Java支持提供了轻量级开发体验,GitHub Cop等AI辅助编程工具提高了Java开发效率。现代IDE如IntelliJ IDEA提供了强大的代码分析和重构功能。这些改进使Java开发体验更加现代化,减少了与新兴语言在这方面的差距。第五章 未来展望与学习建议5.1 Java的未来发展轨迹基于当前的发展趋势,Java的未来呈现出几个明确的发展方向。首先,Java将继续巩固其在企业级应用开发中的地位,通过持续改进和生态扩展保持领先优势。大型企业系统的迁移成本极高,这种惯性将成为Java的天然护城河。其次,Java将加速向云原生环境演进。通过Project Loom、GraalVM原生镜像等新技术,Java将更好地适应容器化和无服务器计算环境,夺回在这些新兴领域的竞争力。第三,Java将继续扩展其应用领域。通过改进的本地代码互操作性和向量计算支持,Java将进入传统上由C++和Fortran主导的高性能计算领域。在边缘计算和物联网领域,Java也有巨大潜力。最后,Java将保持其开放性和多样性。多语言运行时的支持使Java能够与其他语言和谐共存,形成一个更加丰富和多样的生态系统。5.2 对Java爱好者们的建议对于现有的Java开发者,以下建议可能有助于应对未来的变化:深入理解Java核心概念和JVM工作原理至关重要。语言特性会变化,但核心原理的价值是持久的。深入了解内存模型、垃圾回收机制和并发模型将使你能够更好地利用Java的新特性。拥抱云原生技术和实践。学习容器化、Kubernetes和无服务器计算将使你能够将Java应用成功部署到现代云环境中。掌握Spring Boot、Quarkus或Micronaut等框架的云原生特性。探索新特性并适时采用。虚拟线程、原生镜像等新特性具有革命性潜力,及早掌握这些技术将为你的职业发展带来优势。但同时要注意评估新特性的成熟度和适用性,避免在生产环境中过早采用。拓宽技术视野,学习互补技术。Java开发者应该了解Python、Go或JavaScript等其他语言,以及数据库、消息队列和分布式系统等相关技术。全栈开发能力越来越有价值。参与Java社区和开源项目。通过参与社区活动、贡献开源项目,你不仅可以扩展知识面,还能建立专业网络,了解行业最新动态。5.3 对初学者的建议对于考虑学习Java的初学者,以下建议可能有所帮助:Java仍然是值得学习的语言。其广泛的应用领域、丰富的就业机会和稳定的生态系统使其成为可靠的职业选择。学习Java为你打开了进入大型企业开发世界的大门。从基础开始,循序渐进。先掌握Java核心概念和面向对象编程原理,再学习常用框架和工具。实践是最好的学习方式,通过实际项目巩固理论知识。不要局限于Java语言本身。了解JVM工作原理、学习设计模式、掌握软件工程最佳实践同样重要。这些知识将使你成为更好的开发者,无论使用什么语言。建立完整的技术栈。除了Java,还应该学习前端技术、数据库、DevOps工具等相关技能。全栈开发者在就业市场上更具竞争力。参与实践项目和社区。通过GitHub贡献开源项目、参与编程社区讨论、完成实际项目,你将获得宝贵经验和反馈。这些实践经验往往比理论知识更有价值。结论Java的霸主地位虽然面临挑战,但远未被撼动。其强大的生态系统、持续的语言创新和企业级应用的深厚根基构成了难以复制的竞争优势。Project Loom的虚拟线程、GraalVM的原生镜像等新特性不仅解决了Java的传统痛点,还为其开辟了新的应用领域。在可预见的未来,Java仍将是企业级应用开发的主导力量,同时在云原生、边缘计算等新兴领域不断扩大影响力。对于开发者而言,Java仍然是值得投入时间和精力学习的语言,提供了稳定的职业发展和丰富的创新机会。技术的世界永远充满变数,但Java通过持续演进和生态扩展证明了其适应性和生命力。无论是Java爱好者还是初学者,都可以对Java的未来保持乐观,同时以开放的心态拥抱变化和多语言共存的未来。参考资料Oracle. (2023). “Java Language and Virtual Machine Specifications”Microsoft. (2023). “Java on Azure Development Guide”VMware. (2023). “Spring Framework Reference Documentation”Red Hat. (2023). “Quarkus: Supersonic Subatomic Java”Oracle Labs. (2023). “Project Loom: Fibers and Continuations for the Java Virtual Machine”GraalVM Team. (2023). “GraalVM: Run Programs Faster Anywhere”JetBrains. (2023). “The State of Developer Ecosystem 2023”TIOBE Software. (2023). “TIOBE Index for Programming Languages”GitHub. (2023). “The State of the Octoverse”Stack Overflow. (2023). “Stack Overflow Developer Survey 2023”————————————————原文链接:https://blog.csdn.net/lilinhai548/article/details/150699267
  • [技术干货] 如何找出Java进程中占用CPU或内存最多的线程?
    在排查 Java 性能问题时,定位占用 CPU 或内存最多的线程是关键步骤。以下是针对两种场景的具体排查方法:一、找出占用 CPU 最高的线程步骤 1:找到 Java 进程 ID(PID)ps -ef | grep java       # 查找所有 Java 进程top -c | grep java       # 实时监控 Java 进程(按 CPU 排序)AI运行代码步骤 2:找出进程内占用 CPU 最高的线程top -Hp <PID>           # 按 H 键切换到线程模式,按 CPU 排序(默认)AI运行代码关键指标:%CPU 列显示线程的 CPU 使用率。记录占用最高的线程 ID(如 12345)。步骤 3:将线程 ID 转换为 16 进制printf "%x\n" <TID>     # 例如:printf "%x\n" 12345  →  3039AI运行代码步骤 4:使用 jstack 导出线程堆栈jstack <PID> | grep -A 30 'nid=0x3039'   # 查看线程堆栈AI运行代码输出分析:"http-nio-8080-exec-1" #10 daemon prio=5 os_prio=0 tid=0x00007f9c000b4800 nid=0x3039 runnable [0x00007f9bf9e2e000]   java.lang.Thread.State: RUNNABLE   at java.net.SocketInputStream.socketRead0(Native Method)   at java.net.SocketInputStream.read(SocketInputStream.java:150)   ...AI运行代码 关键信息:线程名称(如 http-nio-8080-exec-1)、状态(RUNNABLE)、堆栈顶方法(如 socketRead0)。二、找出占用内存最多的线程方法 1:通过线程堆栈分析(适用于内存泄漏)生成堆转储文件(Heap Dump)jmap -dump:format=b,file=heap.hprof <PID>    # 生成堆快照AI运行代码使用工具分析(如 MAT、VisualVM)MAT(Memory Analyzer Tool):java -jar mat.app/Contents/Eclipse/MemoryAnalyzer -data /tmp/mat_workspace heap.hprofAI运行代码关键步骤:打开堆文件 → Leak Suspects 报告。查看 "Threads" 选项卡,按线程分组查看内存占用。定位持有大量对象的线程(如线程池中的工作线程)。方法 2:通过线程分配统计(JDK 11+)启用线程内存分配统计java -XX:StartFlightRecording=settings=profile,filename=recording.jfr -XX:ThreadAllocationStatisticsSamplingInterval=1000 YourMainClassAI运行代码使用 JMC(Java Mission Control)分析jmc recording.jfrAI运行代码 关键步骤:打开 JFR 文件 → 线程 → 内存分配。按分配量排序,找出占用最多的线程。三、自动化脚本工具脚本 1:一键查找高 CPU 线程#!/bin/bash# 功能:找出 Java 进程中占用 CPU 最高的线程并显示堆栈PID=$1 if [ -z "$PID" ]; then    echo "用法: $0 <Java 进程 PID>"    exit 1fi # 获取占用 CPU 最高的线程 IDTID=$(top -Hp $PID -b -n 1 | awk 'NR>7 {print $1; exit}')if [ -z "$TID" ]; then    echo "未找到进程 $PID 或无活动线程"    exit 1fi # 转换为 16 进制HEX_TID=$(printf "%x\n" $TID)echo "CPU 占用最高的线程 ID: $TID (0x$HEX_TID)" # 打印线程堆栈echo "堆栈信息:"jstack $PID | grep -A 30 "nid=0x$HEX_TID"AI运行代码脚本 2:定期监控线程内存分配#!/bin/bash# 功能:定期生成堆快照并分析PID=$1INTERVAL=${2:-300}  # 默认 5 分钟 if [ -z "$PID" ]; then    echo "用法: $0 <Java 进程 PID> [间隔秒数]"    exit 1fi while true; do    TIMESTAMP=$(date +%Y%m%d_%H%M%S)    HEAP_FILE="heap_${PID}_${TIMESTAMP}.hprof"        echo "[$TIMESTAMP] 生成堆快照: $HEAP_FILE"    jmap -dump:format=b,file=$HEAP_FILE $PID        # 分析最大线程(简化版,实际需用 MAT 等工具)    echo "[$TIMESTAMP] 分析中..."    # TODO: 调用 MAT API 或其他工具分析堆文件        echo "[$TIMESTAMP] 下次采样将在 $INTERVAL 秒后..."    sleep $INTERVALdoneAI运行代码四、常见问题场景与解决方案问题场景    排查方法    解决方案线程池满载导致 CPU 飙升    1. 查看线程名是否包含 pool 或 executor2. 检查堆栈是否卡在任务执行中    1. 增加线程池大小2. 优化任务执行逻辑3. 使用有界队列避免无限提交任务GC 线程频繁触发    1. 查看 GC 线程 CPU 使用率2. 使用 jstat 观察 GC 频率    1. 增加堆内存2. 调整 GC 算法(如 G1、ZGC)3. 优化对象创建模式阻塞线程导致内存堆积    1. 分析堆转储中的大对象2. 检查线程是否长时间持有锁    1. 优化锁粒度2. 使用无锁数据结构3. 增加生产者 / 消费者队列容量五、注意事项性能影响:jstack 会触发安全点(Safepoint),可能导致应用短暂停顿。频繁生成堆转储会占用大量磁盘空间并影响性能。工具版本兼容性:使用与目标 JVM 相同版本的工具(如 JDK 8 的 jstack 分析 JDK 11 的进程可能有问题)。生产环境建议:优先使用非侵入式工具(如 JFR、异步采样)。高负载时避免同时执行多个诊断命令。通过以上方法,可快速定位问题线程并进行针对性优化,提升 Java 应用的稳定性和性能。————————————————原文链接:https://blog.csdn.net/ygq13572549874/article/details/148032263
  • [技术干货] Java融合PostgreSQL:节气与季节检索的实战应用
    前言        在当今数字化时代,信息检索技术在各个领域都发挥着至关重要的作用。从简单的网页搜索到复杂的数据库查询,检索技术的高效性和准确性直接影响着用户体验和工作效率。在众多检索领域中,对自然现象的检索,如节气与季节的检索,虽然看似简单,却蕴含着丰富的文化内涵和实用价值。         节气是中国古代农耕文明的智慧结晶,它将一年分为二十四个节气,每个节气大约15天。这些节气不仅反映了自然界的气候变化,还与农业生产、民俗文化等密切相关。例如,“立春”标志着春天的开始,是播种的季节;“冬至”则是一年中白天最短、夜晚最长的日子,有着丰富的民俗活动。季节的划分则是更宏观的时间单位,通常分为春、夏、秋、冬四季,每个季节大约三个月。季节的变化影响着人们的日常生活、农业生产以及自然生态系统的平衡。         随着信息技术的发展,人们对于节气与季节信息的需求不再局限于传统的日历和书籍。一个高效、便捷的检索系统能够帮助用户快速获取与节气和季节相关的信息,无论是了解某个节气的习俗,还是查询某个季节的气候特征,都能在短时间内得到满足。这不仅有助于传承和弘扬传统文化,还能为现代生活提供实用的参考。         本文将探讨如何利用Java与PostgreSQL的融合,实现一个高效、准确且具有文化价值的节气与季节检索系统。通过Java与PostgreSQL的融合,我们成功实现了一个节气与季节检索系统。这个系统不仅能够快速、准确地检索到用户所需的信息,还具有良好的用户体验。它为用户提供了了解节气与季节的便捷途径,有助于传承和弘扬传统文化,同时也为现代生活提供了实用的参考。 一、节气和季节知识小课堂        关于节气和季节大家多少都能说上一些内容,那么这里我们依然还是简单给大家讲讲节气和季节的知识,当然作为技术人员,许多的知识点没那么详细,主要是为了在进行下一个主题的介绍时,能有一个基本的知识共识。 1、节气是什么        节气是中国古代农耕文明的智慧结晶,是根据太阳在黄道上的位置划分的。一年分为二十四个节气,每个节气大约15天。这些节气不仅反映了自然界的气候变化,还与农业生产、民俗文化等密切相关。例如,“立春”标志着春天的开始,是播种的季节;“冬至”则是一年中白天最短、夜晚最长的日子,有着丰富的民俗活动。每个节气都有其独特的气候特征和文化内涵。例如,“惊蛰”时,春雷初响,惊醒冬眠中的昆虫;“芒种”时,麦子成熟,需要及时收割。节气的变化提醒着人们关注自然,顺应自然规律,安排农事活动和生活起居。节气的划分体现了中国古代人民对自然规律的深刻理解和尊重,是中国传统文化的重要组成部分。 2、季节是什么        季节是根据地球绕太阳公转的轨道位置和地球自转轴的倾斜角度划分的,通常分为春、夏、秋、冬四季,每个季节大约三个月。季节的变化主要体现在气候特征上,如春季温暖湿润,夏季炎热多雨,秋季凉爽干燥,冬季寒冷少雨。这些气候特征影响着自然生态系统的平衡,也决定了人们的日常生活和农业生产。例如,春季是万物复苏的季节,适合播种和植树;夏季是生长的季节,农作物快速生长,人们需要防暑降温;秋季是收获的季节,农作物成熟,人们忙着收割;冬季是休养生息的季节,人们需要保暖和储备食物。季节的划分是人们根据长期的自然观察和生活经验总结出来的,是人类适应自然环境的重要方式之一。 3、节气和季节的关系        节气和季节虽然都是时间的划分方式,但它们之间存在着密切的关系。节气是季节的细分,二十四个节气分布在四季之中,每个季节包含六个节气。例如,春季包括立春、雨水、惊蛰、春分、清明和谷雨;夏季包括立夏、小满、芒种、夏至、小暑和大暑;秋季包括立秋、处暑、白露、秋分、寒露和霜降;冬季包括立冬、小雪、大雪、冬至、小寒和大寒。节气的变化更细致地反映了季节内的气候变化和自然现象,为人们提供了更精确的时间参考。同时,节气和季节的划分都与农业生产密切相关,它们共同指导着农民的农事活动。例如,在春季的立春时节,农民开始准备春耕;在夏季的芒种时节,农民忙着收割小麦并播种水稻。节气和季节的结合,不仅体现了中国古代人民对自然规律的深刻理解,也展现了他们对农业生产的科学安排。两者相辅相成,共同构成了中国古代时间观念的重要组成部分。关于节气和季节的关系,可以看下面这张图:         这其中就表示季节的节气组成,一年有四季,而每个季节又有六个节气,如此循环反复,让我们周而复始,生生不息。这是按照我国传统的四季的划分,这里无异于进行四季的具体的划分,仅从一种角度进行划分。有了这个数据基础之后,下面我们就如何表示季节和节气的关系进行PostgreSQL数据库进行设计和实现。 二、PostgreSQL数据库设计        我们来看看2025年的全年节气信息表,可以通过搜索引擎或者大模型进行输出,通过万年历也可以的,下面分享一个来自kimi的2025年节气与时间关系: ('立春', '2025-02-04'),('雨水', '2025-02-19'),('惊蛰', '2025-03-05'),('春分', '2025-03-20'),('清明', '2025-04-04'),('谷雨', '2025-04-20'),('立夏', '2025-05-05'),('小满', '2025-05-21'),('芒种', '2025-06-05'),('夏至', '2025-06-21'),('小暑', '2025-07-07'),('大暑', '2025-07-23'),('立秋', '2025-08-07'),('处暑', '2025-08-23'),('白露', '2025-09-08'),('秋分', '2025-09-23'),('寒露', '2025-10-08'),('霜降', '2025-10-23'),('立冬', '2025-11-07'),('小雪', '2025-11-22'),('大雪', '2025-12-07'),('冬至', '2025-12-21'),('小寒', '2026-01-05'),('大寒', '2026-01-20');AI运行代码bash        仔细看以上信息是比较简单的,这里我们以PostgreSQL数据库为例,重点讲解如何在数据层,PostgreSQL数据库被用来存储节气与季节的相关数据。这些数据包括节气的名称、日期、习俗描述等。通过合理设计数据库表结构和索引,确保数据的存储和检索效率,以年度为例,只需要存储24条记录即可,数据的量不是很大,在现实中时可以接受的。 1、信息存储         存储节气信息的表结构脚本如下: /*==============================================================*//* Table: biz_solarterms                                        *//*==============================================================*/create table biz_solarterms (   pk_id                INT8                 not null,   solar_term           VARCHAR(5)           not null,   solar_date           TIMESTAMP            not null,   constraint PK_BIZ_SOLARTERMS primary key (pk_id));comment on table biz_solarterms is'节气时间信息表,用于存储节气及其对应的日期';comment on column biz_solarterms.pk_id is'主键';comment on column biz_solarterms.solar_term is'节气信息';comment on column biz_solarterms.solar_date is'节气日期';AI运行代码sql         以上是节气对应日期信息表,该表是计算节气的核心表。 2、数据示例        接下来,我们模拟将2025年的全年的节气信息与其时间信息录入到数据库中,这里提供快速的录入SQL脚本: INSERT INTO biz_solarterms (pk_id,solar_term, solar_date) VALUES(1,'立春', '2025-02-04'),(2,'雨水', '2025-02-19'),(3,'惊蛰', '2025-03-05'),(4,'春分', '2025-03-20'),(5,'清明', '2025-04-04'),(6,'谷雨', '2025-04-20'),(7,'立夏', '2025-05-05'),(8,'小满', '2025-05-21'),(9,'芒种', '2025-06-05'),(10,'夏至', '2025-06-21'),(11,'小暑', '2025-07-07'),(12,'大暑', '2025-07-23'),(13,'立秋', '2025-08-07'),(14,'处暑', '2025-08-23'),(15,'白露', '2025-09-08'),(16,'秋分', '2025-09-23'),(17,'寒露', '2025-10-08'),(18,'霜降', '2025-10-23'),(19,'立冬', '2025-11-07'),(20,'小雪', '2025-11-22'),(21,'大雪', '2025-12-07'),(22,'冬至', '2025-12-21'),(23,'小寒', '2026-01-05'),(24,'大寒', '2026-01-20');AI运行代码sql        以上为演示数据,有的节气日期不一定准确,请大家按实际情况进行修改录入。 3、SQL查询        有了这些数据之后,我们如何指定一个日期比如某天,它究竟是在哪个节气当中呢?这里就需要需要数据库查询来简化操作,这里提供一种查询思路,SQL查询语句如下: WITH ordered_terms AS (    SELECT solar_term, solar_date,           LEAD(solar_date) OVER (ORDER BY solar_date) AS next_date    FROM biz_solarterms)SELECT solar_term, solar_date, next_dateFROM ordered_terms WHERE to_date('2025-09-10','YYYY-MM-DD') >= solar_date  AND (to_date('2025-09-10','YYYY-MM-DD') < next_date OR next_date IS NULL);AI运行代码sql        这里需要使用with语句来进行查询嵌套,在参数中指定查询日期后就可以查询到当前日期到底属于哪个节气。比如以上SQL在客户端软件中执行之后,可以看到如下结果: solar_term solar_date next_date白露 2025-09-08 00:00:00 2025-09-23 00:00:00AI运行代码bash        查询结果表明,2025-09-10在白露节气当中,白露的开始日期是9月8日,到9月23日结束。有了SQL的查询结果之后,下面我们结合JAVA来讲讲结合PostgreSQL数据库如何来求解季节信息。 三、JAVA计算节气和季节        在众多编程语言和数据库管理系统中,Java和PostgreSQL的组合是一个理想的选择。Java是一种广泛使用的高级编程语言,具有跨平台性、面向对象、多线程等优点。它强大的功能和丰富的库支持,使得开发复杂的应用程序变得相对容易。PostgreSQL则是一种开源的对象关系型数据库管理系统,以其高性能、高可靠性和强大的功能而闻名。它支持复杂的查询,能够高效地处理大量数据,非常适合用于存储和检索节气与季节信息。在应用开发中,我们通常使用Java来进行应用开发,因此这里我们重点讲讲如何使用Java来计算节气和季节。 1、模型层查询接节气        模型层的定义比较简单,这里贴出数据库查询Mapper类的主要代码: package com.yelang.project.extend.earthquake.mapper;import org.apache.ibatis.annotations.Param;import org.apache.ibatis.annotations.Select;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.yelang.project.extend.earthquake.domain.SolarTerms;/** * - 节气时间信息mapper接口 * @author 夜郎king */public interface SolarTermsMapper extends BaseMapper<SolarTerms>{static final String FIND_SOLARTERMS_BYDATE = "<script>"+ " WITH ordered_terms AS ( SELECT solar_term, solar_date,LEAD(solar_date) OVER (ORDER BY solar_date) AS next_date "+ " FROM biz_solarterms ) SELECT solar_term, solar_date, next_date FROM ordered_terms "+ " <![CDATA[ WHERE to_date(#{queryDate},'YYYY-MM-DD') >= solar_date AND (to_date(#{queryDate},'YYYY-MM-DD') < next_date OR next_date IS NULL) ]]> "// 需要增加转义+ "</script>";/*** - 根据日期查询所处节气   add by 夜郎king in 2025-09-10* @param queryDate查询日期* @return 该时间所处的节气信息*/@Select(FIND_SOLARTERMS_BYDATE)SolarTerms findSolarTermsByDate(@Param("queryDate")String queryDate);}AI运行代码java         这里需要注意的是,由于这里有时间的判断查询,因此我们引入格式化的标签,以方便>和<这种符号能正常解析使用。 2、根据节气反推季节根据前面的知识可以知晓,季节与节气其实有一个对应关系的,在前面的图中有明确的表示,那么如何使用Java来进行计算呢?比如我们已经使用前面的方法查询出指定日期属于哪个节气,那么如何通过节气推出季节呢?可以看看下面的核心代码: @Overridepublic String getSeason(String solarTerm) {switch (solarTerm) {case "立春":case "雨水":case "惊蛰":case "春分":case "清明":case "谷雨":return "春季";case "立夏":case "小满":case "芒种":case "夏至":case "小暑":case "大暑":return "夏季";case "立秋":case "处暑":case "白露":case "秋分":case "寒露":case "霜降":return "秋季";case "立冬":case "小雪":case "大雪":case "冬至":case "小寒":case "大寒":return "冬季";default:return "未知";}}AI运行代码java 3、节气及季节检索        最后使用Junit程序来测试一下以上的程序是否可靠,是否按照我们的预期进行查询和展示。首先分享查询调用方法: package com.yelang.project.date;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;import com.yelang.project.extend.earthquake.domain.SolarTerms;import com.yelang.project.extend.earthquake.service.ISolarTermsService;@SpringBootTest@RunWith(SpringRunner.class)public class SolarTermSeasonDbCase {@Autowiredprivate ISolarTermsService solarTermsService;@Testpublic void testGetSolar() {SolarTerms solarTerms = solarTermsService.findSolarTermsByDate("2025-07-10");System.out.println(solarTerms);System.out.println(solarTermsService.getSeason(solarTerms.getSolarTerm()));solarTerms = solarTermsService.findSolarTermsByDate("2025-10-08");System.out.println(solarTerms);System.out.println(solarTermsService.getSeason(solarTerms.getSolarTerm()));solarTerms = solarTermsService.findSolarTermsByDate("2025-11-23");System.out.println(solarTerms);System.out.println(solarTermsService.getSeason(solarTerms.getSolarTerm()));}}AI运行代码java         这里模拟了三个不同的日期,以验证计算的效果是正确,在IDE中运行以上程序后,在控制台中可以看到以下输出:         至此,基于Java进行季节和节气的计算就基本完成,可以正常的进行日期的转换,同时经过万年历等验证,基本是正确的。 四、总结        以上就是本文的主要内容,通过Java与PostgreSQL的融合,成功实现了一个节气与季节检索系统。这个系统不仅能够快速、准确地检索到用户所需的信息,还具有良好的用户体验。在未来的工作中,我们计划进一步优化系统的性能,增加更多的功能,如用户自定义查询、数据可视化等。我们还将探索与其他技术的结合,如人工智能和大数据分析,以进一步提升系统的智能化水平。我们相信,随着技术的不断发展和创新,节气与季节检索系统将为更多的人带来便利和价值。行文仓促,定有不足之处,欢迎各位朋友在评论区批评指正,不胜感激。————————————————原文链接:https://blog.csdn.net/yelangkingwuzuhu/article/details/151453058
  • [技术干货] Java内功修炼——线程安全三剑客:synchronized、volatile与wait/notify
    1.线程安全1.1 概念&示例概念:指在多线程环境下,某个代码、函数或对象能够被多个线程同时调用或访问时,仍能保持正确的行为和数据一致性。简单来说,线程安全的代码在多线程环境下运行可靠,不会因线程间的交互而产生不可预测的结果 示例: public class ThreadDemo {    public static int count = 0;    public static void main(String[] args) throws InterruptedException {        Thread thread1 = new Thread(()->{            for (int i = 0; i < 500000; i++) {                count++;            }        });        Thread thread2 = new Thread(()->{            for (int i = 0; i < 500000; i++) {                count++;            }        });        thread1.start();        thread2.start();        thread1.join();        thread2.join();        System.out.println("count = " + count);//每次执行的结果都不一致    }}AI运行代码java 按照上述代码的逻辑,期望得到的结果是1000000,但实际计算的结果与期望值不一致 线程不安全:当多个线程同时访问或修改共享资源时,由于缺乏适当的同步机制,可能导致程序行为不可预测、数据损坏或错误结果的现象 1.2 线程不安全的原因1.访问修改共享变量:当多个线程同时读写同一内存区域时,可能导致数据状态不一致2.原子性:原子性指一个操作是不可分割的单元,要么完全执行,要么完全不执行。如果操作不是原子的,在并发环境下,线程可能被中断在中间状态,导致部分修改3.内存可见性:在多线程编程中,每个线程都有自己的工作内存(本地内存),用于存储共享变量的副本。由于CPU缓存、编译器优化等因素,操作可能只发生在工作内存中,而不是直接在主内存中进行,导致程序行为不符合预期4.指令重排序:是计算机处理器或编译器为了提高程序执行效率,对指令执行顺序进行优化的一种技术。在保证程序最终结果正确的前提下,允许指令的执行顺序与代码编写的顺序不一致。但可能导致多线程下的逻辑错误5.线程之间抢占式执行:这是操作系统层面的调度机制,线程的执行顺序是随机的和不可预测的。操作系统可能随时中断一个线程(抢占),切换到另一个线程执行。一般不轻易改变,当引发线程安全时优先考虑前4个原因共享变量访问修改是线程安全问题的前提,但需结合2/3/4才会引发问题;抢占式执行是线程调度的特性,无法避免 2.synchronized关键字2.1 概念synchronized(监视器锁monitor lock):用于实现线程同步,确保多线程环境下对共享资源的访问安全。通过加锁机制,防止多个线程同时访问同步块代码或对象,避免数据不一致问题 2.2 特性2.2.1 原子性确保了代码块的原子性,即被同步的代码块在执行过程中不会被其他线程中断。这意味着在一个线程执行完整个同步块之前,其他线程无法进入同一个同步块,从而保证了操作的完整性 public class ThreadDemo {    //锁对象    private static final Object locker = new Object();    private static int count = 0;    public static void main(String[] args) throws InterruptedException {        Thread thread1 = new Thread(()->{            for (int i = 0; i < 500000; i++) {                synchronized (locker) {                    count++;                }            }        });        Thread thread2 = new Thread(()->{            for (int i = 0; i < 500000; i++) {                synchronized (locker) {                    count++;                }            }        });        thread1.start();        thread2.start();        thread1.join();        thread2.join();        System.out.println("count = " + count);//1000000    }}AI运行代码java 2.2.2 内存可见性获取锁时:线程会将工作内存中的变量副本失效,强制从主内存重新读取最新值释放锁时:线程会将工作内存中修改过的变量刷新到主内存这种机制确保了共享变量的修改对所有线程立即可见 2.2.3 互斥性确保在同一时间只有一个线程可以进入被同步的代码块或方法,这意味着当一个线程进入同步块或方法时,其他试图进入同一同步块的线程会被阻塞,直到第一个线程退出同步块 2.2.4 可重入性synchronized关键字是可重入的,这意味着如果一个线程已经持有某个对象的锁,那么它可以再次获取该对象的锁,而不会被阻塞 可重入锁通常会维护一个计数器,记录当前线程获取锁的次数。每次获取锁时,计数器加一;释放锁时,计数器减一。当计数器为零时,锁才真正被释放public class Reentry_Lock {    public static void main(String[] args) {        Object locker = new Object();        Thread thread = new Thread(()->{            synchronized (locker){                System.out.println("第一层锁");                synchronized (locker){                    System.out.println("第二层锁");                }            }        });        thread.start();    }}AI运行代码java 2.3 类型2.3.1 实例锁作用于对象实例,每个对象实例拥有自己的锁。当一个线程访问对象的synchronized实例方法或代码块时,其他线程无法访问该对象的其他synchronized方法或代码块,但可以访问非synchronized方法或代码块 public class Example {    // 实例方法锁    public synchronized void instanceMethod() {        // 同步代码    }    // 实例代码块锁    public void anotherMethod() {        synchronized (this) {            // 同步代码        }    }}AI运行代码java 2.3.2 静态锁作用于类的Class对象,所有实例共享同一把锁。当一个线程访问synchronized静态方法或代码块时,其他线程无法访问该类的其他synchronized静态方法或代码块,但可以访问非synchronized静态方法或代码块 public class Example {    // 静态方法锁    public static synchronized void staticMethod() {        // 同步代码    }    // 静态代码块锁    public static void anotherStaticMethod() {        synchronized (Example.class) {            // 同步代码        }    }}AI运行代码java 2.4 死锁概念:指两个或多个线程在执行过程中,因争夺资源而造成的一种互相等待的现象,导致这些线程都无法继续执行下去。这种情况下,系统资源被占用,但程序无法继续运行 死锁产生的必要条件: 1.互斥条件(Mutual Exclusion):至少有一个资源必须处于非共享模式,即一次只能被一个线程使用。如果另一个线程请求该资源,那么请求线程必须等待,直到该资源被释放2.请求与保持条件(Hold and Wait):一个线程已经持有至少一个资源,并且正在等待获取其他被其他线程占用的资源3.不可剥夺条件(No Preemption):资源一旦被分配给某个线程,就不能被强制性地剥夺,只能由占有该资源的线程自行释放4.环路等待条件(Circular Wait):存在一个线程资源的循环等待链,其中每个线程都在等待下一个进程所持有的资源预防死锁:通过破坏死锁的四个必要条件之一,可以预防死锁的发生 1.破坏互斥条件:尽量使用可共享的资源2.破坏占有且等待:一次性申请所有需要的资源,避免部分持有3.破坏非抢占条件(不建议):允许系统强行剥夺某些进程已占有的资源,分配给其他进程。这种方法可能导致进程执行的不稳定性4.破坏循环等待条件:对资源进行排序,按固定顺序申请资源3.volatile3.1 概念volatile:是编程语言中的关键字,用于修饰变量,告知编译器该变量可能被意外修改。其核心作用是防止编译器优化导致的数据不一致问题(在Java中仅能修饰成员变量) 3.2 特性3.2.1 内存可见性对volatile变量的每次访问都会强制从主内存读取,每次修改都会立即写回主内存 public class demo_volatile {    //每次访问都会强制从主内存读取,每次修改都会立即写回主内存    //去除volatile关键字会导致thread1线程在访问num时不从主内存读取    public static volatile int num = 0;     public static void main(String[] args) {        //thread1线程的生命周期掌握在thread2手中        Thread thread1 = new Thread(()->{            while (num == 0){}            System.out.println("Over thread1");        });        Thread thread2 = new Thread(()->{            System.out.println("请输入一个整数");            Scanner in = new Scanner(System.in);            num = in.nextInt();        });        thread1.start();        thread2.start();    }}AI运行代码java 3.2.2 禁止指令重排序在多线程场景下,指令重排序可能会导致线程间数据同步问题。volatile变量通过插入内存屏障(Memory Barrier)来禁止重排序 读操作前插入“LoadLoad”屏障,读操作后插入“LoadStore”屏障写操作前插入“StoreStore”屏障,写操作后插入“StoreLoad”屏障public class FixedReorderingExample {    int a = 0;//普通变量    int b = 0;//普通变量    volatile boolean flag = false;//标志变量使用volatile    // 写线程方法    public void writer() {        a = 1;        b = 1;        flag = true;//volatile 写,插入写屏障:确保flag写操作在a、b写操作之后    }boolean demo = true    // 读线程方法    public void reader() {        if (flag) {//volatile 读,插入读屏障:确保println读操作不会再if读操作之前            int r1 = a;            int r2 = b;            System.out.println("r1: " + r1 + ", r2: " + r2);//总是输出 r1: 1, r2: 1        }    }}AI运行代码java 3.2.3 不保证原子性volatile不保证操作的原子性,多线程环境下仍需结合锁或原子操作 4.wait/notify概念:wait()和notify()是Java中用于线程间通信的机制,属于Object类的方法。它们必须在同步代码块(如synchronized块)中使用,否则会抛出IllegalMonitorStateException wait(): 让当前线程进入等待状态,释放锁,直到其他线程调用notify()或notifyAll()唤醒它notify(): 随机唤醒一个等待该对象锁的线程notifyAll(): 唤醒所有等待该对象锁的线程public class Demo {    public static void main(String[] args) throws InterruptedException {        Object locker = new Object();        Thread thread1 = new Thread(()->{            //thread1拿到锁            synchronized (locker) {                System.out.println("thread1线程wait之前");                try {                    //thread1释放锁,进入waiting状态,等待被唤醒                    locker.wait();                } catch (InterruptedException e) {                    throw new RuntimeException(e);                }                System.out.println("thread1线程wait之后");            }        });        Thread thread2 = new Thread(()->{            //thread1进入waiting之后,thread2拿到锁            synchronized (locker){                System.out.println("thread2线程notify之前");                //虽然notify执行之后thread1被唤醒了,但此时仍处于thread2的synchronized中                //同一对象才能唤醒                locker.notify();                System.out.println("thread2线程notify之后");            }        });        thread1.start();        Thread.sleep(1000);        thread2.start();    }}AI运行代码java wait与sleep的区别 概念:wait是Object类的方法,用于线程间的通信,必须配合synchronized使用,调用wait的线程会释放锁sleep是Thread类的静态方法,用于暂停当前线程的执行,调用sleep的线程不会释放锁锁的释放行为差异:wait会释放当前线程持有的锁,允许其他线程获取该锁并执行同步代码块,这一特性使得wait适用于多线程协作的场景sleep不会释放任何锁,即使线程休眠,其他线程也无法获取该线程持有的锁,这可能导致死锁或性能问题唤醒机制:wait需要通过notify或notifyAll主动唤醒,否则线程会一直等待(可以设置最大等待时间),唤醒后线程需要重新获取锁才能继续执行sleep无需外部唤醒,到达指定时间后自动恢复,恢复执行的线程直接从sleep调用处继续执行————————————————原文链接:https://blog.csdn.net/2401_89167985/article/details/150501838
总条数:764 到第
上滑加载中