SpringBoot采用集成多个Mybatis框架实现多JDBC数据源 + 多数据源本地事务

目录

  • 1. 原理
    • 1.1 实现多数据源的原理
    • 1.2 多数据源本地事务的原理
  • 2. Mysql数据准备
  • 3. pom.xml依赖
  • 4. application.properties配置
  • 5. 创建两个SqlSessionFactory
    • 5.1 DataSource1Config
    • 5.1 DataSource2Config
  • 6. 动态数据源测试
    • 6.1 创建User类
    • 6.2 Mapper接口实现
    • 6.3 Mapper.xml实现
    • 6.4 Service实现
    • 6.5 测试
  • 7. AOP + 自定义注解实现事务(未测试)

1. 原理

1.1 实现多数据源的原理

通过启动多个SqlSessionFactoryBean,每个SqlSessionFactoryBean对应一个datasource和指定位置的mapper.xml文件,就可以实现多个数据源了。而不用切换数据源,不用实现AbstractRoutingDataSource

1.2 多数据源本地事务的原理

在多数据源下,涉及到多个数据库的写入。Spring的声明式事务在一次请求线程中只能对一个数据源进行控制。因为一个DataSourceTransactionManager无法完成对多数据源的控制

需要多个DataSourceTransactionManager可以完成,但@Transactional注解无法管理多个数据源,这里我们通过变通的方式让@Transactional管理多个DataSourceTransactionManager

将每个datasource都分别和一个DataSourceTransactionManager进行绑定。然后可以通过嵌套事务的方式进行调用

2. Mysql数据准备

分别创建write_db1.user和write_db2.user

mysql> create database write_db1;
Query OK, 1 row affected (0.14 sec)mysql> create database write_db2;
Query OK, 1 row affected (0.01 sec)mysql> create table write_db1.user (-> id bigint(20) auto_increment not null comment '主键ID',-> name varchar(30) null default null comment '姓名',-> primary key (id)-> );
Query OK, 0 rows affected, 1 warning (0.29 sec)mysql> 
mysql> create table write_db2.user (-> id bigint(20) auto_increment not null comment '主键ID',-> name varchar(30) null default null comment '姓名',-> primary key (id)-> );
Query OK, 0 rows affected, 1 warning (0.04 sec)mysql> 

3. pom.xml依赖

        <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.31</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.15</version></dependency><!-- 支持spring 2.5.3 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.2</version></dependency><!-- 用于编程式事务 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>

4. application.properties配置

指定了datasource1和datasource2两个DataSource

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource# 用于读的数据库
spring.datasource.datasource1.url=jdbc:mysql://192.168.28.12:3306/write_db1
spring.datasource.datasource1.username=root
spring.datasource.datasource1.password=Root_123
spring.datasource.datasource1.driver-class-name=com.mysql.cj.jdbc.Driver# 用于写的数据库
spring.datasource.datasource2.url=jdbc:mysql://192.168.28.12:3306/write_db2
spring.datasource.datasource2.username=root
spring.datasource.datasource2.password=Root_123
spring.datasource.datasource2.driver-class-name=com.mysql.cj.jdbc.Driver

5. 创建两个SqlSessionFactory

5.1 DataSource1Config

其实就是将datasource1和指定的Mapper接口、Mapper.xml文件进行绑定。然后将datasource1TransactionManager和datasource1绑定,datasource1TransactionTemplate和datasource1TransactionManager进行绑定

说明:

  • 通过@MapperScan注解,指定Mapper接口的位置和SqlSessionFactory的名称
  • 指定了要连接的数据源datasource1
  • 指定了Mapper.xml文件的位置
package com.hh.springboottest.config;import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;import javax.sql.DataSource;@Configuration
@MapperScan(basePackages = {"com.hh.springboottest.mapper.datasource1"},sqlSessionFactoryRef = "datasource1SqlSessionFactory")
public class DataSource1Config {@ConfigurationProperties(prefix = "spring.datasource.datasource1")// 向IOC容器添加name = dataSource1的DataSource@Beanpublic DataSource dataSource1() {DruidDataSource druidDataSource = new DruidDataSource();return druidDataSource;}@Bean@Primarypublic SqlSessionFactory datasource1SqlSessionFactory(@Qualifier("dataSource1") DataSource dataSource) throws Exception {final SqlSessionFactoryBean datasource1SqlSessionFactoryBean = new SqlSessionFactoryBean();// 指定数据源datasource1SqlSessionFactoryBean.setDataSource(dataSource);// 指定数据源对应的mapper.xml文件datasource1SqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/datasource1/*.xml"));return datasource1SqlSessionFactoryBean.getObject();}@Bean@Primarypublic DataSourceTransactionManager datasource1TransactionManager() {DataSourceTransactionManager datasource1TransactionManager =new DataSourceTransactionManager();datasource1TransactionManager.setDataSource(dataSource1());return datasource1TransactionManager;}@Beanpublic TransactionTemplate datasource1TransactionTemplate() {return new TransactionTemplate(datasource1TransactionManager());}}

5.1 DataSource2Config

package com.hh.springboottest.config;import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;import javax.sql.DataSource;@Configuration
@MapperScan(basePackages = {"com.hh.springboottest.mapper.datasource2"},sqlSessionFactoryRef = "datasource2SqlSessionFactory")
public class DataSource2Config {@ConfigurationProperties(prefix = "spring.datasource.datasource2")// 向IOC容器添加name = dataSource2的DataSource@Beanpublic DataSource dataSource2() {DruidDataSource druidDataSource = new DruidDataSource();return druidDataSource;}@Beanpublic SqlSessionFactory datasource2SqlSessionFactory(@Qualifier("dataSource2") DataSource dataSource) throws Exception {final SqlSessionFactoryBean datasource2SqlSessionFactoryBean = new SqlSessionFactoryBean();// 指定数据源datasource2SqlSessionFactoryBean.setDataSource(dataSource);// 指定数据源对应的mapper.xml文件datasource2SqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/datasource2/*.xml"));return datasource2SqlSessionFactoryBean.getObject();}@Beanpublic DataSourceTransactionManager datasource2TransactionManager() {DataSourceTransactionManager datasource2TransactionManager =new DataSourceTransactionManager();datasource2TransactionManager.setDataSource(dataSource2());return datasource2TransactionManager;}@Beanpublic TransactionTemplate datasource2TransactionTemplate() {return new TransactionTemplate(datasource2TransactionManager());}}

6. 动态数据源测试

6.1 创建User类

package com.hh.springboottest.myController;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;@NoArgsConstructor
@AllArgsConstructor
@Data
@ToString
public class User {private Long id;private String name;}

6.2 Mapper接口实现

Datasource1UserMapper.java

package com.hh.springboottest.mapper.datasource1;import com.hh.springboottest.myController.User;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface Datasource1UserMapper {public void saveUser(User user);}

Datasource2UserMapper.java

package com.hh.springboottest.mapper.datasource2;import com.hh.springboottest.myController.User;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface Datasource2UserMapper {public void saveUser(User user);
}

6.3 Mapper.xml实现

resources/mapper/datasource1/Datasource1UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hh.springboottest.mapper.datasource1.Datasource1UserMapper"><!-- public void saveUser(User user); --><insert id="saveUser" parameterType="com.hh.springboottest.myController.User">INSERT INTO user(id, name) VALUES(#{id}, #{name})</insert></mapper>

resources/mapper/datasource2/Datasource2UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hh.springboottest.mapper.datasource2.Datasource2UserMapper"><!-- public void saveUser(User user); --><insert id="saveUser" parameterType="com.hh.springboottest.myController.User">INSERT INTO user(id, name) VALUES(#{id}, #{name})</insert></mapper>

6.4 Service实现

Service接口实现

package com.hh.springboottest.service;import com.hh.springboottest.myController.User;public interface UserService {// 向datasource1的数据库插入数据public void datasource1SaveUser(User user);// 向datasource2的数据库插入数据public void datasource2SaveUser(User user);// 用于调用datasource1SaveUser和datasource2SaveUserpublic void saveMultiUser();// 嵌套在saveMultiUser里面进行调用public void saveMultiUserInner();
}

ServiceImpl实现类

说明:

  • 编程式事务:通过两层lambda表达式嵌套,实现两个数据源的统一事务管理,对异常进行捕获,然后手动进行回滚
  • 声明式事务:通过两层事务注解方法进行嵌套,实现两个数据源的统一事务管理。不能进行异常捕获,自动进行回滚,然后抛出异常
package com.hh.springboottest.service.impl;import com.hh.springboottest.mapper.datasource1.Datasource1UserMapper;
import com.hh.springboottest.mapper.datasource2.Datasource2UserMapper;
import com.hh.springboottest.myController.User;
import com.hh.springboottest.service.UserService;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;@Service
public class UserServiceImpl implements UserService {@AutowiredDatasource1UserMapper datasource1UserMapper;@AutowiredDatasource2UserMapper datasource2UserMapper;@AutowiredTransactionTemplate datasource1TransactionTemplate;@AutowiredTransactionTemplate datasource2TransactionTemplate;/*** @Description: 向datasource1的数据库插入数据** @Params:    [user]* @Return:    void*/@Overridepublic void datasource1SaveUser(User user) {datasource1UserMapper.saveUser(user);}/*** @Description: 向datasource2的数据库插入数据** @Params:    [user]* @Return:    void*/@Overridepublic void datasource2SaveUser(User user) {datasource2UserMapper.saveUser(user);}/*** @Description: 用于调用datasource1SaveUser和datasource2SaveUser  *      * 编程式事务方式。被0除,回滚所以插入的数据** @Params:    []* @Return:    void*/
//    public void saveMultiUser() {
//        datasource1TransactionTemplate.execute((datasource1Status)->{
//            datasource2TransactionTemplate.execute((datasource2Status)->{
//                try {
//                    datasource1SaveUser(new User(1L, "read_name1"));
//                    datasource2SaveUser(new User(1L, "write_name1"));
//                    datasource1SaveUser(new User(2L, "read_name2"));
//                    datasource2SaveUser(new User(2L, "write_name2"));
//                    Integer d = 1 / 0;
//                } catch (Exception e) {
//                    e.printStackTrace();
//                    datasource1Status.setRollbackOnly();
//                    datasource2Status.setRollbackOnly();
//                    return false;
//                }
//                return true;
//            });
//            return true;
//        });
//    }/*** @Description: 用于调用datasource1SaveUser和datasource2SaveUser** 声明式事务方式。被0除,回滚所以插入的数据** @Params:    []* @Return:    void*/@Transactional(transactionManager = "datasource1TransactionManager")public void saveMultiUser() {UserService currentUserService = (UserService) AopContext.currentProxy();currentUserService.saveMultiUserInner();}/*** @Description: 嵌套在saveMultiUser里面进行调用  * * @Params:    []* @Return:    void*/@Transactional(transactionManager = "datasource2TransactionManager")public void saveMultiUserInner() {datasource1SaveUser(new User(1L, "read_name1"));datasource2SaveUser(new User(1L, "write_name1"));datasource1SaveUser(new User(2L, "read_name2"));datasource2SaveUser(new User(2L, "write_name2"));Integer myDiv = 1 / 0;}}

6.5 测试

说明:

  • EnableTransactionManagement:开启事务控制功能
  • EnableAspectJAutoProxy注解表示开启AOP功能,exposeProxy为true表示让proxy被AOP框架当做ThreadLocal进行暴露,以便通过org.springframework.aop.framework.AopContext类进行获取,默认关闭,不能通过本类调用本类
package com.hh.springboottest;import com.hh.springboottest.myController.User;
import com.hh.springboottest.service.UserService;
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 org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.transaction.annotation.EnableTransactionManagement;@Slf4j
@SpringBootTest
@EnableTransactionManagement   
@EnableAspectJAutoProxy(exposeProxy = true)
public class MyApplicationTest {@AutowiredUserService userService;@Testpublic void saveMultiUserTest() {userService.saveMultiUser();}}

运行程序,结果如下:

2022-12-13 22:04:29.857  INFO 21468 --- [           main] com.alibaba.druid.pool.DruidDataSource   : {dataSource-2} initedjava.lang.ArithmeticException: / by zeroat com.hh.springboottest.service.impl.UserServiceImpl.saveMultiUserInner(UserServiceImpl.java:117)
......省略部分......

数据库未插入一条数据,因为回滚了

7. AOP + 自定义注解实现事务(未测试)

这里只做了一部分的记录,并未运行进行测试

自定注解,然后通过多线程的方式执行多个事务方法

package com.hh.springboottest.aop;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
@Aspect
public class MultiTransactionAop {// ComboTransaction类需要自己定义,还未定义private final ComboTransaction comboTransaction;@Autowiredpublic MultiTransactionAop(ComboTransaction comboTransaction) {this.comboTransaction = comboTransaction}@Pointcut("within(com.hh.springboottest.service.impl.*)")public void pointCut() {}@Around("pointCut() && @annotation(multiTransactional)")public Object inMultiTransactions(ProceedingJoinPoint pjp, MultiTransactional multiTransactional) {return comboTransaction.inCombinedTx(() -> {try {return pjp.proceed();       // 执行目标方法} catch (Throwable throwable) {if (throwable instanceof RuntimeException) {throw (RuntimeException) throwable;}throw new RuntimeException(throwable);}}, multiTransactional.value());}}

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

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

相关文章

【狂神说】HTML详解

目录 1 HTML概述1.1 什么是HTML1.2 HTML发展史1.3 HTML5的优势1.4 W3C标准 2 网页2.1 网页基本信息2.2 网页基本标签2.2.1 标题标签2.2.2 段落标签2.2.3 换行标签2.2.4 水平线标签2.2.5 字体样式标签&#xff1a;粗体、斜体2.2.6 注释和特殊符号 2.3 图像标签2.4 链接标签邮箱链…

C#串口原理

串口实际有2种。主要是电压逻辑不一样。玩单片机的人指usb转TTL的串口&#xff1b;普通人指USB转DB9的串口&#xff1b;先看下他们的区别&#xff1a; https://doc.embedfire.com/module/module_tutorial/zh/latest/Module_Manual/port_class/serial_port.html 1. 串口外设总结…

PC网站微信扫码登录

PC站通过微信扫码登录网站。 参考文档&#xff1a;微信官方文档 1.通过在PC端打开以下链接&#xff1a; https://open.weixin.qq.com/connect/qrconnect?appidAPPID&redirect_uriREDIRECT_URI&response_typecode&scopeSCOPE&stateSTATE#wechat_redirect 用…

Flink的基于两阶段提交协议的事务数据汇实现

背景 在flink中可以通过使用事务性数据汇实现精准一次的保证&#xff0c;本文基于Kakfa的事务处理来看一下在Flink 内部如何实现基于两阶段提交协议的事务性数据汇. flink kafka事务性数据汇的实现 1。首先在开始进行快照的时候也就是收到checkpoint通知的时候&#xff0c;在…

15.项目讲解之前端页面的实现

项目讲解之前端页面的实现 本项目前端使用HBuilerX软件编写HBuilderX下载安装配置一键直达&#xff0c; uniapp框架uniapp官网&#xff0c; 使用Element-ui组件Element-ui组件网址进行前端页面的完成。 前端项目下载地址 前端项目 前端项目展示 首页 首页展示 echarts实现…

简单的数学运算如何改变算法

简单的数学运算如何影响事物 当你坐在无人驾驶汽车上行驶时&#xff0c;突然发现前面有一个问题。一个亚马逊快递司机将他们的货车开到了一辆双停的UPS卡车旁边&#xff0c;然后才意识到无法通过。现在他们卡住了&#xff0c;你也卡住了。 街道太窄&#xff0c;无法实现U型转弯…

CCF CSP认证 历年题目自练Day32

题目一 试题编号&#xff1a; 202209-1 试题名称&#xff1a; 如此编码 时间限制&#xff1a; 1.0s 内存限制&#xff1a; 512.0MB 问题描述&#xff1a; 题目背景 某次测验后&#xff0c;顿顿老师在黑板上留下了一串数字 23333 便飘然而去。凝望着这个神秘数字&#xff0c;小…

C# Convert和BitConverter类学习

前言&#xff1a; C# Convert是一个比较好用的强制转换&#xff0c;相比我们之前用的(int)或者是类型.Parse()&#xff0c;Convert给我们提供了很多的选项&#xff0c;特别是对于有字节要求的变量&#xff0c;Convert简直就是C#编程的福音&#xff0c;BitConvert对于byte数组转…

linux下文件存储系统(inode/目录项/硬链接)

概念&#xff1a; 关键点&#xff1a; &#xff08;1&#xff09;inode 也叫做文件属性管理结构体 &#xff08;2&#xff09;目录项里面存两个东西 文件名和 inode号。通过inode号可以找到磁盘上的文件。 &#xff08;3&#xff09;给文件创建硬链接的时候&#xff0c;两个…

中国矿业大学-JAVA期末备考

JAVA里面&#xff0c;“”和“equals"的区别是什么呢&#xff1f; 1.""操作符用于比较两个对象的引用是否相等。也就是说&#xff0c;它会检查两个对象是否指向内存中的同一个地址。如果两个对象的引用完全相同&#xff0c;则""返回true&#xff1b;否…

uniapp 小程序实现图片宽度100%、高度自适应的效果

因为image组件默认是有宽度跟高度的&#xff0c;所以这个高度不怎么好写 通过load事件来控制图片的高度 话不多说&#xff0c;直接上代码&#xff0c; <image class"img" src"/static/image.png" :style"{ height: imgHeight px }"mode&q…

CentOS 7 服务器上创建新用户及设置用户密码有效期

一、创建用户 1、以 root 用户身份登录到 CentOS 服务器 2、运行以下命令以创建新用户&#xff1a; useradd -m -s /bin/bash username其中&#xff0c;username 是您要创建的新用户的用户名。该命令将创建一个新用户并为其分配一个主目录。3、运行以下命令以设置新用户的密码…

Frame Buffer设备驱动 (ili9488 3.5寸tft屏)

Frame Buffer设备驱动 Frame Buffer设备ili9488介绍驱动编写代码编写ili9488.c设备树修改测试ili9488代码分析 LCD资料下载 Frame Buffer设备 在早期的输出显示设备中&#xff0c;大部分为CRT显示器&#xff0c;随着技术的不断发展&#xff0c;现在大部分使用的是液晶显示器。这…

MySQL视图、用户管理和C语言链接

文章目录 1. 视图1.1 基本使用 2. 用户管理2.1 用户信息2.2 创建用户2.3 修改用户密码2.4 删除用户 3. 数据库的权限3.1 给用户授权3.2 回收权限 4. mysql connect4.1 Connector/C 使用4.2 mysql接口介绍 1. 视图 视图是一个虚拟表&#xff0c;其内容由查询定义。同真实的表一…

百度SEO优化的特点(方式及排名诀窍详解)

百度SEO优化的特点介绍&#xff1a; 百度SEO优化是指对网站进行优化&#xff0c;使其在百度搜索引擎中获得更好的排名&#xff0c;进而获取更多的流量和用户。百度SEO优化的特点是综合性强、效果持久、成本低廉、投资回报高。百度的搜索算法不断更新&#xff0c;所以长期稳定的…

开源任务调度框架

本文主要介绍一下任务调度框架Flowjob的整体结构&#xff0c;以及整体的心路历程。 功能介绍 flowjob主要用于搭建统一的任务调度平台&#xff0c;方便各个业务方进行接入使用。 项目在设计的时候&#xff0c;考虑了扩展性、稳定性、伸缩性等相关问题&#xff0c;可以作为公司…

YOLOv5网络结构图

网络结构图&#xff08;简易版和详细版&#xff09; 网络框架介绍 前言&#xff1a; YOLOv5是一种基于轻量级卷积神经网络&#xff08;CNN&#xff09;的目标检测算法&#xff0c;整体可以分为三个部分&#xff0c; backbone&#xff0c;neck&#xff0c;head。 如上图所示…

C++——多态

多态是有继承关系的类对象调用相同的函数&#xff0c;会有不同的结果。例如&#xff0c;普通人买高铁票不打折&#xff0c;学生打75折&#xff0c;儿童免费&#xff0c;这种情况就适合使用多态 虚函数 被virtual修饰的类成员函数 class A{ public:virtual void fun() {} }; …

【深度学习】深度学习实验四——循环神经网络(RNN)、dataloader、长短期记忆网络(LSTM)、门控循环单元(GRU)、超参数对比

一、实验内容 实验内容包含要进行什么实验,实验的目的是什么,实验用到的算法及其原理的简单介绍。 1.1 循环神经网络 (1)理解序列数据处理方法,补全面向对象编程中的缺失代码,并使用torch自带数据工具将数据封装为dataloader。 (2)分别采用手动方式以及调用接口方式…

Redis--List、Set、Zset、Hash、Bitmaps、HyperLogLog、Geospatial

List LPUSH key value1 [value2] 将一个或多个值插入到列表头部 127.0.0.1:6379> LPUSH myls1 1 (integer) 1 127.0.0.1:6379> LPUSH myls1 2 (integer) 2 127.0.0.1:6379> LRANGE myls1 0 -1 1) "2" 2) "1" LPOP key 移出并获取列表的第一个元素…