springboot+mybatis+redis实现二级缓存

news2024/11/28 22:49:27

Mybatis提供了对缓存的支持,分为一级缓存和二级缓存,其查询顺序为:二级缓存>一级缓存->数据库,最原始是直接查询数据库,为了提高效率和节省资源,引入了一级缓存,为了进一步提高效率,引入了二级缓存。
Mybatis一级缓存是sqlSession级别的缓存,缓存的数据只在sqlSession内有效。
Mybatis二级缓存是mapper级别的缓存,同一个namespace公用这一个缓存,对sqlSession是共享的。
Mybatis默认开启一级缓存,默认没有开启二级缓存,需要在setting全局参数中配置开启二级缓存。

1、二级缓存

Mybatis二级缓存是多个sqlSession共享的,其作用域是mapper的同一个namespace,即同一个namespace下的所有操作语句都影响着同一个cache,即二级缓存被多个sqlSession共享,是一个全局的变量。
不同的sqlSession两次执行相同namespace下的sql语句且向sql中传递参数也相同时,即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。
二级缓存默认是关闭二级缓存的,因为对应增删改操作频繁的话,那么二级缓存形同虚设,每次都会被清空缓存。
在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将 MyBatis的Cache 接口实现,有一定的开发成本,直接使用Redis、分布式缓存可能成本更低,安全性也更高。

2、实例

2.1、新建springboot工程

在idea中新建springboot工程,版本为2.7.+,引入相关依赖,如下

	<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.3.0</version>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.31</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

在application.properties文件中配置mybatis、redis等

# 端口
server.port=8083

# 数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root

#mybatis
#entity扫描的包名
mybatis.type-aliases-package=com.*.*.model
#Mapper.xml所在的位置
mybatis.mapper-locations=classpath*:/mapper/*Mapper.xml
#开启MyBatis的二级缓存
mybatis.configuration.cache-enabled=true

#日志配置
logging.level.com.*=debug
logging.level.org.springframework.web=debug
logging.level.org.springframework.transaction=debug
logging.level.org.mybatis=debug

#redis
#database name
spring.redis.database=0
#server host
spring.redis.host=127.0.0.1
#server password
spring.redis.password=
#connection port
spring.redis.port=6379

debug=false

2.2、相关配置类

Redis配置类替换序列化实现方式

@Configuration
public class RedisConfig {

    /**
     * 重写Redis序列化方式,使用Json方式:
     * 当我们的数据存储到Redis的时候,我们的键(key)和值(value)都是通过Spring提供的Serializer序列化到数据库的。
     * RedisTemplate默认使用的是JdkSerializationRedisSerializer,StringRedisTemplate默认使用的是StringRedisSerializer。
     * Spring Data JPA为我们提供了下面的Serializer:
     * GenericToStringSerializer、Jackson2JsonRedisSerializer、JacksonJsonRedisSerializer、JdkSerializationRedisSerializer、OxmSerializer、StringRedisSerializer。
     * 在此我们将自己配置RedisTemplate并定义Serializer。
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        // 设置值(value)的序列化采用Jackson2JsonRedisSerializer。
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        // 设置键(key)的序列化采用StringRedisSerializer。
        redisTemplate.setKeySerializer(new StringRedisSerializer());

        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

自定义缓存需要实现Mybatis的Cache接口,这里将使用Redis来作为缓存的容器

public class MybatisRedisCache implements Cache {

    private static final Logger logger = LoggerFactory.getLogger(MybatisRedisCache.class);

    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);

    private RedisTemplate<String, Object> redisTemplate = SpringContextHolder.getBean("redisTemplate");

    private String id;

    public MybatisRedisCache(final String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        logger.info("Redis Cache id " + id);
        this.id = id;
    }

    @Override
    public String getId() {
        return this.id;
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return this.readWriteLock;
    }

    @Override
    public void putObject(Object key, Object value) {
        if (value != null) {
            // 向Redis中添加数据,有效时间是2天
            redisTemplate.opsForValue().set(key.toString(), value, 2, TimeUnit.DAYS);
        }
    }

    @Override
    public Object getObject(Object key) {
        try {
            if (key != null) {
                Object obj = redisTemplate.opsForValue().get(key.toString());
                return obj;
            }
        } catch (Exception e) {
            logger.error("redis ");
        }
        return null;
    }

    @Override
    public Object removeObject(Object key) {
        try {
            if (key != null) {
                redisTemplate.delete(key.toString());
            }
        } catch (Exception e) {
        }
        return null;
    }

    @Override
    public void clear() {
        logger.debug("清空缓存");
        try {
            Set<String> keys = redisTemplate.keys("*:" + this.id + "*");
            if (!CollectionUtils.isEmpty(keys)) {
                redisTemplate.delete(keys);
            }
        } catch (Exception e) {
        }
    }

    @Override
    public int getSize() {
        Long size = (Long) redisTemplate.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                return connection.dbSize();
            }
        });
        return size.intValue();
    }

}

通过Spring
Aware(容器感知)来获取到ApplicationContext,然后根据ApplicationContext获取容器中的Bean

@Component
public class SpringContextHolder implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    /**
     * 实现ApplicationContextAware接口的context注入函数, 将其存入静态变量.
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        // NOSONAR
        SpringContextHolder.applicationContext = applicationContext;
    }

    /**
     * 取得存储在静态变量中的ApplicationContext.
     */
    public static ApplicationContext getApplicationContext() {
        checkApplicationContext();
        return applicationContext;
    }

    /**
     * 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) {
        checkApplicationContext();
        return (T) applicationContext.getBean(name);
    }

    /**
     * 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(Class<T> clazz) {
        checkApplicationContext();
        return (T) applicationContext.getBeansOfType(clazz);
    }

    /**
     * 清除applicationContext静态变量.
     */
    public static void cleanApplicationContext() {
        applicationContext = null;
    }

    private static void checkApplicationContext() {
        if (applicationContext == null) {
            throw new IllegalStateException("applicationContext未注入,请在applicationContext.xml中定义SpringContextHolder");
        }
    }
}

2.3、mapper

实体类

@Data
@ToString
public class Person implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * id
     */
    private Long id;

    /**
     * 名称
     */
    private String name;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 地址
     */
    private String address;

}

Mapper接口

@Mapper
public interface PersonMapper {

    /**
     * 查询
     * @param id
     * @return
     */
    Person selectByPrimaryKey(Long id);

    /**
     * 删除
     * @param id
     * @return
     */
    int deleteByPrimaryKey(Long id);

    /**
     * 更新
     * @param record
     * @return
     */
    int updateByPrimaryKey(Person record);

    /**
     * 插入
     * @param record
     * @return
     */
    int insert(Person record);

    /**
     * 插入
     * @param record
     * @return
     */
    int insertSelective(Person record);

    /**
     * 更新
     * @param record
     * @return
     */
    int updateByPrimaryKeySelective(Person record);

    /**
     * 获取所有数据
     * @return
     */
    List<Person> findAll();

}

Mapper文件

<?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" >
<mapper namespace="com.*.springboot_mybatis_cache.mapper.PersonMapper">

    <cache type="com.*.springboot_mybatis_cache.common.MybatisRedisCache">
        <property name="eviction" value="LRU" />
        <property name="flushInterval" value="6000000" />
        <property name="size" value="1024" />
        <property name="readOnly" value="false" />
    </cache>

    <resultMap id="BaseResultMap" type="com.*.springboot_mybatis_cache.model.Person">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="name" property="name" jdbcType="VARCHAR"/>
        <result column="age" property="age" jdbcType="INTEGER"/>
        <result column="address" property="address" jdbcType="VARCHAR"/>
    </resultMap>

    <sql id="Base_Column_List">
        id, name, age, address
    </sql>

    <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Long">
        select
        <include refid="Base_Column_List"/>
        from person
        where id = #{id,jdbcType=BIGINT}
    </select>

    <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
        delete from person
        where id = #{id,jdbcType=BIGINT}
    </delete>

    <update id="updateByPrimaryKey" parameterType="com.*.springboot_mybatis_cache.model.Person">
        update person
        set name = #{name,jdbcType=VARCHAR},
            age = #{age,jdbcType=INTEGER},
            address = #{address,jdbcType=VARCHAR}
        where id = #{id,jdbcType=BIGINT}
    </update>

    <insert id="insert" parameterType="com.*.springboot_mybatis_cache.model.Person">
        <selectKey resultType="java.lang.Long" keyProperty="id" order="AFTER">
            SELECT LAST_INSERT_ID()
        </selectKey>
        insert into person (name, age, address)
        values (#{name,jdbcType=VARCHAR}, #{age,jdbcType=INTEGER}, #{address,jdbcType=VARCHAR})
    </insert>

    <insert id="insertSelective" parameterType="com.*.springboot_mybatis_cache.model.Person">
        <selectKey resultType="java.lang.Long" keyProperty="id" order="AFTER">
            SELECT LAST_INSERT_ID()
        </selectKey>
        insert into person
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="name != null">
                name,
            </if>
            <if test="age != null">
                age,
            </if>
            <if test="address != null">
                address,
            </if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            <if test="name != null">
                #{name,jdbcType=VARCHAR},
            </if>
            <if test="age != null">
                #{age,jdbcType=INTEGER},
            </if>
            <if test="address != null">
                #{address,jdbcType=VARCHAR},
            </if>
        </trim>
    </insert>

    <update id="updateByPrimaryKeySelective" parameterType="com.*.springboot_mybatis_cache.model.Person">
        update person
        <set>
            <if test="name != null">
                name = #{name,jdbcType=VARCHAR},
            </if>
            <if test="age != null">
                age = #{age,jdbcType=INTEGER},
            </if>
            <if test="address != null">
                address = #{address,jdbcType=VARCHAR},
            </if>
        </set>
        where id = #{id,jdbcType=BIGINT}
    </update>

    <!-- 对这个语句useCache="true"默认是true,可以不写 -->
    <select id="findAll" resultMap="BaseResultMap" useCache="true">
        select
        <include refid="Base_Column_List"/>
        from person
    </select>

</mapper>

2.4、service

Service接口

public interface PersonService {

    /**
     * 查询
     * @return
     */
    List<Person> findAll();

    /**
     * 插入
     * @param person
     */
    void insert(Person person);
}

Service实现类

@Service
@Transactional(readOnly = true)
public class PersonServiceImpl implements PersonService {

    @Autowired
    private PersonMapper personMapper;

    @Override
    public List<Person> findAll() {
        return personMapper.findAll();
    }

    @Override
    @Transactional
    public void insert(Person person) {
        personMapper.insert(person);
    }

}

2.5、测试

	private Logger logger = LoggerFactory.getLogger(SpringbootMybatisCacheApplicationTests.class);

    @Autowired
    private PersonService personService;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Test
    public void testInsert() {
        Person person = new Person();
        person.setName("测试");
        person.setAddress("address");
        person.setAge(10);
        personService.insert(person);
        logger.debug(JSON.toJSONString(person));
    }

    @Test
    public void testFindAll() {
        List<Person> persons = personService.findAll();
        logger.debug(JSON.toJSONString(persons));
    }

    // 测试mybatis缓存
    @Test
    public void testCache() {
        long begin = System.currentTimeMillis();
        List<Person> persons = personService.findAll();
        long ing = System.currentTimeMillis();
        personService.findAll();
        long end = System.currentTimeMillis();
        logger.debug("第一次请求时间:" + (ing - begin) + "ms");
        logger.debug("第二次请求时间:" + (end - ing) + "ms");
        logger.debug(JSON.toJSONString(persons));
    }

    // 测试Redis存储和获取一个List
    @Test
    public void testRedisCacheSetList() {
        List<Person> persons = new ArrayList<>();
        Person person = new Person();
        person.setName("测试");
        person.setAddress("address");
        person.setAge(10);
        persons.add(person);
        persons.add(person);
        persons.add(person);
        redisTemplate.opsForValue().set("111", persons, 2, TimeUnit.MINUTES);
        persons = (List<Person>) redisTemplate.opsForValue().get("111");
        System.out.println(JSON.toJSONString(persons));
    }

    // 测试Redis存储和获取一个Object
    @Test
    public void testRedisCacheSetObject() {
        Person person = new Person();
        person.setName("测试");
        person.setAddress("address");
        person.setAge(10);
        redisTemplate.opsForValue().set("222", person, 2, TimeUnit.MINUTES);
        Object p = redisTemplate.opsForValue().get("222");
        if (p instanceof Person) {
            Person person1 = (Person) p;
            System.out.println(JSON.toJSONString(person1));
        }
    }

    // 测试通过SpringAware获取Spring容器中的额Bean
    @Test
    public void testApplicationContextAware() {
        RedisTemplate redisTemplate = SpringContextHolder.getBean("redisTemplate");
        System.out.println(redisTemplate);
    }

测试mybatis缓存结果类似如下
在这里插入图片描述

3、小结

实际开发中,MyBatis 通常和 Spring 进行整合开发。Spring 将事务放到 Service 中管理,对于每一个 service 中的 sqlSession 是不同的,这是通过 mybatis-spring 中的 org.mybatis.spring.mapper.MapperScannerConfigurer 创建 sqlsession 自动注入到 service 中的。每次查询之后都要进行关闭 sqlSession ,关闭之后数据被清空。所以 spring 整合之后,如果没有事务,一级缓存是没有意义的。那么如果开启二级缓存,关闭 sqlsession 后,会把该 sqlsession 一级缓存中的数据添加到namespace 的二级缓存中。这样,缓存在sqlsession关闭之后依然存在。
二级缓存的应用场景多用于查询多于修改时的场景,因为任何增删改操作都将刷新二级缓存,对二级缓存的频繁刷新将降低系统性能。
感兴趣的小伙伴可以尝试一下~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/532989.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机网络(四下)——网络层

接上篇&#xff0c;这篇文章主要来写路由选择 五、路由协议 1>动态路由 1.距离向量算法&#xff08;RIP协议&#xff09;&#xff1b;适用于小型网络 1》规定&#xff1a; 1>记录跳数(Hop count)最少的路径。 2>RIP允许一条路由最多15个路由器&#xff0c;距离为…

LitCTF2023 wp re最后一道 cry misc

本来不打算放了&#xff0c;但是比赛打都打了留个纪念社工有佬&#xff0c;与我无关&#xff0c;misc只会隐写虽然我是逆向手&#xff0c;但因为队友tql&#xff0c;所以只留给我最后一道~~我的wp向来以简述思路为主&#xff0c;习惯就好 Crypto Hex&#xff1f;Hex&#xff…

【项目设计】 负载均衡在线OJ系统

&#x1f9f8;&#x1f9f8;&#x1f9f8;各位大佬大家好&#xff0c;我是猪皮兄弟&#x1f9f8;&#x1f9f8;&#x1f9f8; 文章目录 一、项目介绍项目技术栈和开发环境 二、项目的宏观结构三、compile_server模块①日志模块开发&#xff0c;Util工具类&#xff0c;供所以模…

NVIDIA再现谜之刀法,RTX 4060Ti新增16G版

随着上一代库存逐渐清理到位&#xff0c;苏妈与老黄终于要把新一代主流级显卡掏出来了。 根据外网消息&#xff0c;AMD 这边主要是 RX 7600XT 与 7600 等型号&#xff0c;发布日期定为 5 月 25 日。 AMD 保密措施做得挺到位的&#xff0c;目前除了部分厂商爆出的包装与产品图…

[MYAQL / Mariadb] 数据库学习-数据导入导出

数据库学习-数据导入导出 数据导入导出&#xff08;批量处理数据&#xff09;查看默认检索目录模糊查询&#xff1a;show variables like %XXXX%;修改检索目录路径&#xff08;&#xff01;&#xff01;文件一定要有MySQL用户的 7的RWX 权限&#xff01;&#xff09;默认的检索…

前端-02 CSS基础

1 简介 1.1 CSS语法 语法 选择器&#xff1a;HTML元素 生命块&#xff1a;用;隔开的各种声明 {a;b} 每条声明有CSS属性名称和值&#xff0c;用冒号分割{属性:值;属性:值} 案例 整块代码 <!DOCTYPE html> <html><head><style>body {background…

同一个IP可以安装配置多个SSL证书吗?

如何在同一IP地址上运行多个SSL证书? 服务器名称指示SNI&#xff0c;可以帮助您实现同一IP运行多个SSL证书&#xff0c;这样虚拟主机网站也能用上SSL证书了。 什么是SNI 服务器名称指示SNI是SSL的一个重要组成部分&#xff0c;SNI允许多个网站存在于同一个IP地址上&#xff…

CVPR目标检测经典作:HOG特征

来源&#xff1a;投稿 作者&#xff1a;小灰灰 编辑&#xff1a;学姐 HOG特征 HOG特征( Histogram of Oriented Gradients 方向梯度直方图&#xff09;是一种在图像上找到特征描述子&#xff0c;主要通过计算和统计图像局部区域的梯度方向直方图来构成特征。来源于cvpr2015 年…

Angular 学习笔记

本系列笔记主要参考&#xff1a; Angular学习视频 Angular官方文档 Angular系列笔记 特此感谢&#xff01; 目录 1.Angular 介绍2.Angular 环境搭建、创建 Angular 项目、运行 Angular 项目2.1.环境搭建2.2.创建 Angular 项目2.3.运行项目 3.Angular 目录结构分析3.1.目录结构分…

低分辨率视频可以变高分辨率吗?

近几年&#xff0c;老电影、老视频片段修复越来越常见了。很多优质的片源&#xff0c;因为年代久远&#xff0c;分辨率较低&#xff0c;画质比较差&#xff0c;通过视频超分技术&#xff0c;实现了画质增强&#xff0c;提高画质分辨率&#xff0c;视频画面变得更清晰了。 首先…

计算机有哪些方面的技术? - 易智编译EaseEditing

计算机是一种多功能的电子设备&#xff0c;可以处理数据、进行信息存储和检索、进行计算和模拟等多种任务。计算机技术是指计算机相关的技术领域&#xff0c;包括硬件和软件等多个方面。下面介绍一些常见的计算机技术&#xff1a; 操作系统技术&#xff1a; 操作系统是计算机系…

select poll epoll有什么区别

select/poll select 实现多路复用的方式是&#xff0c;将已连接的 Socket 都放到一个文件描述符集合&#xff0c;然后调用 select 函数将文件描述符集合拷贝到内核里&#xff0c;让内核来检查是否有网络事件产生&#xff0c;检查的方式很粗暴&#xff0c;就是通过遍历文件描述…

什么是日志文件

文章目录 什么是日志文件Centos 7 日志文件简易说明日志文件的重要性Linux常见的日志文件文件名/var/log/boot.log/var/log/cron/var/log/dmesg/var/log/lastlog/var/log/maillog或 /var/log/mail/*/var/log/messages/var/log/secure/var/log/wtmp、/var/log/faillog/var/log/h…

Netty实战(二)

第一个Netty程序 一、环境准备二、Netty 客户端/服务器概览三、编写 Echo 服务器3.1 ChannelHandler 和业务逻辑3.2 引导服务器 四、编写 Echo 客户端4.1 通过 ChannelHandler 实现客户端逻辑4.2 引导客户端 五、构建和运行 Echo 服务器和客户端 一、环境准备 Netty需要的运行…

U盘怎么加密?最简单的U盘加密方法

说起U盘&#xff0c;相信每个人都不会感到陌生&#xff0c;它是最常用的移动存储设备。那么&#xff0c;你会加密U盘吗&#xff1f;相信不少人并不知道这个问题的答案。下面小编就来教大家自己动手制作加密U盘。 首先&#xff0c;我们需要提前做好准备工作&#xff0c;一个可以…

04-数组和字符串

概述 同一个数组所有的成员都是相同的数据类型&#xff0c;同时所有的成员在内存中的地址是连续的。 一维数组 全局数组若不初始化&#xff0c;编译器将其初始化为零。局部数组若不初始化&#xff0c;内容为随机值。 int a[10] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//定义一…

瑞吉外卖 - 员工信息分页查询功能(7)

某马瑞吉外卖单体架构项目完整开发文档&#xff0c;基于 Spring Boot 2.7.11 JDK 11。预计 5 月 20 日前更新完成&#xff0c;有需要的胖友记得一键三连&#xff0c;关注主页 “瑞吉外卖” 专栏获取最新文章。 相关资料&#xff1a;https://pan.baidu.com/s/1rO1Vytcp67mcw-PD…

NodeJs之调试

关于调试 当我们只专注于前端的时候&#xff0c;我们习惯性F12&#xff0c;这会给我们带来安全与舒心的感觉。 但是当我们使用NodeJs来开发后台的时候&#xff0c;我想噩梦来了。 但是也别泰国担心&#xff0c;NodeJs的调试是很不方便&#xff01;这是肯定的。 但是还好&…

Spring MVC优雅处理业务异常

本文中&#xff0c;我会描述如何在应用程序的不同层次&#xff0c;优雅地处理业务异常。 异常定义 BusinessException基类定义如下&#xff0c;注意异常中携带业务错误码&#xff0c;方便前端处理异常&#xff1a; public class BusinessException extends RuntimeException…

哪些云渲染服务用于多GPU渲染?

众所周知&#xff0c;GPU渲染 可以使用显卡代替CPU进行渲染&#xff0c;可以显着加快渲染速度&#xff0c;因为GPU主要是为快速图像渲染而量身定制的。GPU的诞生是为了应对图形密集型应用程序&#xff0c;这些应用程序会给CPU带来负担并阻碍计算性能。GPU渲染的原理是在多个数据…