• Java精进-20分钟学会mybatis使用


    相关概念

    还是先上文字介绍吧!不敢兴趣的同学可直接跳过

    Mybatis

    MyBatis是一款优秀的基于ORM的半自动轻量级持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。MyBatis可以使用简单的XML或注解来配置和映射原生类型、接口和Java的POJO (Plain Old Java Objects,普通老式Java对 象)为数据库中的记录。

    对象/关系数据库映射(ORM)

    ORM全称Object/Relation Mapping,表示对象-关系映射的缩写ORM完成面向对象的编程语言到关系数据库的映射。

    当ORM框架完成映射后,程序员既可以利用面向对象程序设计语言的简单易用性,又可以利用关系数据库的技术优势。

    ORM把关系数据库包装成面向对象的模型。ORM框架是面向对象设计语言与关系数据库发展不同步时的中间解决方案。

    采用ORM框架后,应用程序不再直接访问底层数据库,而是以面向对象的放松来操作持久化对象,而ORM框架则将这些面向对象的操作转换成底层SQL操作。ORM框架实现的效果:把对持久化对象的保存、修改、删除 等操作,转换为对数据库的操作。

    上面一堆话总结下来,可以理解为 直接操作对象,不用关心数据库细节 。

    Mybatis历史

    原是apache的一个开源项目iBatis, 2010年6月这个项目由apache software foundation 迁移到了google code,随着开发团队转投Google Code旗下,ibatis3.x正式更名为Mybatis ,代码于2013年11月迁移到Github。

    iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAO)

    Mybatis优势

    半自动化,对开发人员开说,核心sql还是需要自己进行优化。

    sql和java编码进行分离,功能边界清晰,一个专注业务,一个专注数据。

    基本使用

    我们通过写一个用户表的增删查改进行入门

    快速入门

    开发步骤如下:

    1. 添加MyBatis的坐标
    2. 创建user数据表
    3. 编写User实体类
    4. 编写映射文件UserMapper.xml
    5. 编写核心文件SqlMapConfig.xml
    6. 编写测试类

    新建maven项目并导入依赖

    
            UTF-8
            UTF-8
            1.8
            1.8
            1.8
         
        
            
                org.mybatis
                mybatis
                3.4.5
             
            
                mysql
                mysql-connector-java
                5.1.6
                runtime
             
            
                junit
                junit
                4.12
                test
             
            
                log4j
                log4j
                1.2.12
            
        

    创建user数据表录入数据,并编写实体

    CREATE TABLE `user` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `username` varchar(50) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
    public class User {
    
        private Integer id;
    
        private String username;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", username='" + username + '\'' +
                    '}';
        }
    }

    编写UserMapper映射文件UserMapper.xml

    
    
    
        
    

    编写MyBatis核心文件sqlMapConfig.xml

    
    
    
        
            
                
                
                
                
                    
                    
                    
                    
                
            
        
        
        
            
        
    

    测试代码

    @Test
        public void test1() throws IOException {
            InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
            //获得sqlSession工厂对象
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
            //获得sqlSession对象
            SqlSession sqlSession = sqlSessionFactory.openSession();
    		//执行sql语句,这里的user.findAll其实是通过xml中namespace和id的组合,
    		//mybatis是通过namespace和id的组合来定位到具体的sql语句的
            List userList = sqlSession.selectList("user.findAll");
            for (User user : userList) {
                System.out.println(user);
            }
            sqlSession.close();
        }

    结果输入

    增删改查操作

    以上我们做了一个简单查询,接下来来个把增删改也来一套!

    插入数据

    基于之前编写UserMapper映射文件,追加insert

    
        
            insert into user values(#{id},#{username},#{password}) 
        
    

    编写测试代码

    @Test
        public void testInsert() throws IOException {
            InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            User user = new User();
            user.setId(3);
            user.setUsername("打工人");
            int insert = sqlSession.insert("user.insert", user);
            System.out.println(insert);
            //提交事务
            sqlSession.commit();
            sqlSession.close();
        }

    总结

    • 插入语句使用insert标签
    • 在映射文件中使用parameterType属性指定要插入的数据类型
    • Sql语句中使用#{实体属性名}方式引用实体中的属性值
    • 插入操作使用的API是sqlSession.insert(“命名空间.id”,实体对象);
    • 插入操作涉及数据库数据变化,所以要使用sqlSession对象显示的提交事务,即sqlSession.commit()

    修改数据

    基于之前编写UserMapper映射文件,追加insert

    
        
            update user set username=#{username} where id=#{id}
        
    

    编写测试代码

    @Test
        public void testUpdate() throws IOException {
            InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            User user = new User();
            user.setId(3);
            user.setUsername("打工人yqy");
            sqlSession.update("user.update", user);
            //提交事务
            sqlSession.commit();
            sqlSession.close();
        }

    总结

    • 修改语句使用update标签

    • 修改操作使用的API是sqlSession.update(“命名空间.id”,实体对象);

    删除数据

    基于之前编写UserMapper映射文件,追加delete

    
        
            delete from user where id=#{id}
        
    

    编写测试代码

    @Test
        public void testDelete() throws IOException {
            InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            sqlSession.delete("user.delete",3);
            //提交事务
            sqlSession.commit();
            sqlSession.close();
        }

    总结

    除语句使用delete标签

    •Sql语句中使用#{任意字符串}方式引用传递的单个参数

    •删除操作使用的API是sqlSession.delete(“命名空间.id”,Object);

    常用配置解析

    environments标签

    数据库环境的配置,支持多环境配置

    事务管理器( transactionManager )类型有两种:

    JDBC:这个配置就是直接使用了JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域。

    MANAGED:这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接,然而一些容器并不希望这样,因此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为。

    数据源( dataSource )类型有三种:

    •UNPOOLED:每次被请求时打开和关闭连接。

    •POOLED:利用“池”的概念将 JDBC 连接对象组织起来。

    •JNDI:为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。

    mapper标签

    该标签的作用是加载映射的,加载方式有如下几种:

    •使用相对于类路径的资源引用,例如:
        
        •使用完全限定资源定位符(URL),例如:
        
        •使用映射器接口实现类的完全限定类名,例如:
        
        •将包内的映射器接口实现全部注册为映射器,例如:
        

    相应API介绍

    SqlSession工厂构建器SqlSessionFactoryBuilder

    常用API:SqlSessionFactory build(InputStream inputStream)

    通过加载mybatis的核心文件的输入流的形式构建一个SqlSessionFactory对象

    String resource = "org/mybatis/builder/mybatis-config.xml"; 
    InputStream inputStream = Resources.getResourceAsStream(resource); 
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); 
    SqlSessionFactory factory = builder.build(inputStream);

    其中, Resources 工具类,这个类在 org.apache.ibatis.io 包中。Resources 类帮助你从类路径下、文件系统或一个 web URL 中加载资源文件。

    SqlSession工厂对象SqlSessionFactory

    SqlSessionFactory 有多个个方法创建SqlSession 实例。常用的有如下两个:

    SqlSession会话对象

    SqlSession中提供了所有执行语句、提交或回滚事务和获取映射器实例的方法。

    执行语句主要有:

     T selectOne(String statement, Object parameter) 
     List selectList(String statement, Object parameter) 
    int insert(String statement, Object parameter) 
    int update(String statement, Object parameter) 
    int delete(String statement, Object parameter)

    操作失事务方法有:

    void commit() 
    void rollback()

    Mybatis的Dao层实现

    实现dao层方式有两种,传统开发和代理开发方式,代理开发的有点是不用编写具体实现类

    传统开发方式

    传统方式即自己编写Dao层接口的实现类

    先定义接口

    public interface UserDao { 
    	List findAll() throws IOException; 
    }

    编写UserDaoImpl实现

    public class UserDaoImpl implements UserDao { 
    	public List findAll() throws IOException { 
    		InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml"); 
    		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); 
    		SqlSession sqlSession = sqlSessionFactory.openSession(); 
    		List userList = sqlSession.selectList("userMapper.findAll"); 
    		sqlSession.close(); 
    		return userList; 
    	} 
    }

    测试

    @Test 
    public void testTraditionDao() throws IOException { 
    	UserDao userDao = new UserDaoImpl(); 
    	List all = userDao.findAll(); 
    	System.out.println(all); 
    }

    代理开发方式

    采用 Mybatis 的代理开发方式实现 DAO 层的开发,这是主流的方式。

    Mapper 接口开发方法只需要编写Mapper 接口(相当于Dao 接口),由Mybatis 框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。

    Mapper 接口开发需要遵循以下规范:

    1. Mapper.xml文件中的namespace与mapper接口的全限定名相同
    2. Mapper接口方法名和Mapper.xml中定义的每个statement的id相同
    3. Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同
    4. Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同

    编写UserMapper接口

    import com.yqy.pojo.User;
    
    public interface UserDao {
        User findById(int id);
    }
    
    	
    

    测试

    @Test
        public void testMapper() throws IOException {
            InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            //获得MyBatis框架生成的UserMapper接口的实现类
            UserDao userDao = sqlSession.getMapper(UserDao.class);
            User user = userDao.findById(1);
            System.out.println(user);
        }

    配置文件深入

    核心配置文件层次关系如下

    常用配置

    Properties标签

    实际开发中,习惯将数据源的配置信息单独抽取成一个properties文件,该标签可以加载额外配置的properties文件

    typeAliases标签

    类型别名是为Java 类型设置一个短的名字。原来的类型名称配置如下

    配置typeAliases,为com.lagou.domain.User定义别名为user

    上面我们是自定义的别名,mybatis框架已经为我们设置好的一些常用的类型的别名

    映射配置文件mapper.xml

    动态sql语句

    根据实体类的不同取值,使用不同的 SQL语句来进行查询。比如在 id如果不为空时可以根据id查询,如果username 不同空时还要加入用户名作为条件。这种情况在我们的多条件组合查询中经常会碰到。

    当查询条件id和username都存在时,控制台打印的sql语句如下:

    当查询条件只有id存在时,控制台打印的sql语句如下:

    看下循环执行sql的拼接操作,例如:SELECT * FROM USER WHERE id IN (1,2,5)。

    测试代码如下:

    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    int[] ids = new int[]{2,5}; 
    List userList = userMapper.findByIds(ids); 
    System.out.println(userList);

    foreach标签的属性含义如下:

    标签用于遍历集合,它的属性:

    •collection:代表要遍历的集合元素,注意编写时不要写#{}

    •open:代表语句的开始部分

    •close:代表结束部分

    •item:代表遍历集合的每个元素,生成的变量名

    •sperator:代表分隔符

    SQL片段抽取

    Sql 中可将重复的 sql 提取出来,使用时用 include 引用即可,最终达到 sql 重用的目的

     
     
     
    

    复杂映射开发

    复杂映射开发主要是对象之间一对一,一对多,多对多关系的处理

    一对一查询

    一对一查询的模型

    用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户一对一查询的需求:查询一个订单,与此同时查询出该订单所属的用户

    一对一查询的语句

    对应的sql语句:select * from orders o,user u where o.uid=u.id;

    查询的结果如下:

    创建Order和User实体

    public class Order { 
    	private int id; 
    	private Date ordertime; 
    	private double total;
    	//代表当前订单从属于哪一个客户 
    	private User user;
    }
    
    public class User { 
    	private int id; 
    	private String username; 
    	private String password; 
    	private Date birthday; 
    }

    创建OrderMapper接口并配置

    public interface OrderMapper { 
    	List findAll();
    }
    
            
                
                
                
                
            
            
    

    还可以配置如下:

    
            
            
            
            
                
                
                
                
            
    

    测试结果

    OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
    List all = mapper.findAll();
    for(Order order : all){ 
     	System.out.println(order); 
    }

    一对多查询

    一对多查询的模型

    用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户

    一对多查询的需求:查询一个用户,与此同时查询出该用户具有的订单

    一对多查询的语句

    对应的sql语句:select *,o.id oid from user u left join orders o on u.id=o.uid;

    查询的结果如下:

    public class Order
    {
        private int id;
        private Date ordertime;
        private double total;
        //代表当前订单从属于哪一个客户 
        private User user;
    }
    
    
    public class User
    {
        private int id;
        private String username;
        private String password;
        private Date birthday;
        //代表当前用户具备哪些订单 
        private List < Order > orderList;
    }

    创建UserMapper接口

    public interface UserMapper { 
    	List findAll(); 
    }

    配置UserMapper.xml

    
            
                
                
                
                
                
                    
                    
                    
                
            
            
        

    测试结果

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List < User > all = mapper.findAll();
    for(User user: all)
    {
        System.out.println(user.getUsername());
        List < Order > orderList = user.getOrderList();
        for(Order order: orderList)
        {
            System.out.println(order);
        }
        System.out.println("----------------------------------");
    }

    多对多查询

    多对多查询的模型

    用户表和角色表的关系为,一个用户有多个角色,一个角色被多个用户使用

    多对多查询的需求:查询用户同时查询出该用户的所有角色

    多对多查询的语句

    对应的sql语句:select u.,r.,r.id rid from user u left join user_role ur on u.id=ur.user_id inner join role r on ur.role_id=r.id;

    查询的结果如下:

    创建Role实体,修改User实体

    public class User
    {
        private int id;
        private String username;
        private String password;
        private Date birthday;
        //代表当前用户具备哪些订单
        private List < Order > orderList;
        //代表当前用户具备哪些角色 
        private List < Role > roleList;
    }
    public class Role
    {
        private int id;
        private String rolename;
    }

    添加UserMapper接口方法

    List findAllUserAndRole();

    配置UserMapper.xml

    
            
            
            
            
            
                
                
            
        
        

    测试结果

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List < User > all = mapper.findAllUserAndRole();
    for(User user: all)
    {
        System.out.println(user.getUsername());
        List < Role > roleList = user.getRoleList();
        for(Role role: roleList)
        {
            System.out.println(role);
        }
        System.out.println("----------------------------------");
    }

    Mybatis注解开发

    MyBatis的常用注解

    这几年来注解开发越来越流行,Mybatis也可以使用注解开发方式,这样我们就可以减少编写Mapper映射文件了。

    我们先围绕一些基本的CRUD来学习,再学习复杂映射多表操作。

    @Insert:实现新增

    @Update:实现更新

    @Delete:实现删除

    @Select:实现查询

    @Result:实现结果集封装

    @Results:可以与@Result 一起使用,封装多个结果集

    @One:实现一对一结果集封装

    @Many:实现一对多结果集封装

    MyBatis的增删改查

    我们完成简单的user表的增删改查的操作

    private UserMapper userMapper;
    
    @Before
    public void before() throws IOException {
    	InputStream resourceAsStream =
    	Resources.getResourceAsStream("SqlMapConfig.xml");
    	SqlSessionFactory sqlSessionFactory = new
    	SqlSessionFactoryBuilder().build(resourceAsStream);
    	SqlSession sqlSession = sqlSessionFactory.openSession(true);
    	userMapper = sqlSession.getMapper(UserMapper.class);
    }
    
    @Test
    public void testAdd() {
    	User user = new User();
    	user.setUsername("测试数据");
    	user.setPassword("123");
    	user.setBirthday(new Date());
    	userMapper.add(user);
    }
    @Test
    public void testUpdate() throws IOException {
    	User user = new User();
    	user.setId( 16 );
    	user.setUsername("测试数据修改");
    	user.setPassword("abc");
    	user.setBirthday(new Date());
    	userMapper.update(user);
    }
    
    public void testDelete() throws IOException {
            userMapper.delete(16);
    }
    
    @Test
    public void testFindById() throws IOException {
        User user = userMapper.findById(1);
        System.out.println(user);
    }
    
    @Test
    public void testFindAll() throws IOException {
        List all = userMapper.findAll();
        for (User user : all) {
            System.out.println(user);
        }
    }

    修改MyBatis的核心配置文件,我们使用了注解替代的映射文件,所以我们只需要加载使用了注解的Mapper接口即可

     
     
    	
    	
    

    或者指定扫描包含映射关系的接口所在的包也可以

    
      
     
      
    

    注解实现复杂映射开发

    实现复杂关系映射之前我们可以在映射文件中通过配置来实现,使用注解开发后,我们可以使用@Results注解,@Result注解,@One注解,@Many注解组合完成复杂关系的配置

    一对一查询

    一对一查询的模型

    用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户

    一对一查询的需求:查询一个订单,与此同时查询出该订单所属的用户

    一对一查询的语句

    对应的sql语句:

    select * from orders;
    
    select * from user where id=查询出订单的uid;

    查询的结果如下:

    public class Order {
    	private int id;
    	private Date ordertime;
    	private double total;
    	private User user;
    }
    public class User {
    	private int id;
    	private String username;
    	private String password;
    	private Date birthday;
    }

    创建OrderMapper接口

    public interface OrderMapper {
    	List findAll();
    }

    使用注解配置Mapper

    public interface OrderMapper {
    	@Select("select * from orders")
    	@Results({
    		@Result(id=true,property = "id",column = "id"),
    		@Result(property = "ordertime",column = "ordertime"),
    		@Result(property = "total",column = "total"),
    		@Result(property = "user",column = "uid",
    		javaType = User.class,
    		one = @One(select = "com.lagou.mapper.UserMapper.findById"))
    	})
    	List findAll();
    }
    public interface UserMapper {
    	@Select("select * from user where id=#{id}")
    		User findById(int id);
    }

    测试结果

    @Test
    public void testSelectOrderAndUser() {
    	List all = orderMapper.findAll();
    	for(Order order : all){
    		System.out.println(order);
    	}
    }

    一对多查询

    一对多查询的模型

    用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户

    一对多查询的需求:查询一个用户,与此同时查询出该用户具有的订单

    6.5.2 一对多查询的语句

    对应的sql语句:

    select * from user;
    select * from orders where uid=查询出用户的id;

    查询的结果如下:

    修改User实体

    public class Order {
    	private int id;
    	private Date ordertime;
    	private double total;
    	//代表当前订单从属于哪一个客户
    	private User user;
    }
    
    
    public class User {
    	private int id;
    	private String username;
    	private String password;
    	private Date birthday;
    	//代表当前用户具备哪些订单
    	private List orderList;
    }

    创建UserMapper接口

    List findAllUserAndOrder();

    使用注解配置Mapper

    public interface UserMapper {
    	@Select("select * from user")
    	@Results({
    		@Result(id = true,property = "id",column = "id"),
    		@Result(property = "username",column = "username"),
    		@Result(property = "password",column = "password"),
    		@Result(property = "birthday",column = "birthday"),
    		@Result(property = "orderList",column = "id",
    			javaType = List.class,
    			many = @Many(select =
    			"com.lagou.mapper.OrderMapper.findByUid"))
    	})
    	List findAllUserAndOrder();
    }
    public interface OrderMapper {
    	@Select("select * from orders where uid=#{uid}")
    	List findByUid(int uid);
    }

    测试结果

    List all = userMapper.findAllUserAndOrder();
    	for(User user : all){
    		System.out.println(user.getUsername());
    		List orderList = user.getOrderList();
    		for(Order order : orderList){
    			System.out.println(order);
    		}
    		System.out.println("-----------------------------");
    	}

    多对多查询

    多对多查询的模型

    用户表和角色表的关系为,一个用户有多个角色,一个角色被多个用户使用

    多对多查询的需求:查询用户同时查询出该用户的所有角色

    多对多查询的语句

    对应的sql语句:

    select * from user;
    select * from role r,user_role ur where r.id=ur.role_id and ur.user_id=用户的id

    查询的结果如下:

    创建Role实体,修改User实体

    public class User {
    	private int id;
    	private String username;
    	private String password;
    	private Date birthday;
    	//代表当前用户具备哪些订单
    	private List orderList;
    	//代表当前用户具备哪些角色
    	private List roleList;
    }
    
    public class Role {
    	private int id;
    	private String rolename;
    }

    添加UserMapper接口方法

    List findAllUserAndRole();

    使用注解配置Mapper

    public interface UserMapper {
    	@Select("select * from user")
    	@Results({
    	@Result(id = true,property = "id",column = "id"),
    	@Result(property = "username",column = "username"),
    	@Result(property = "password",column = "password"),
    	@Result(property = "birthday",column = "birthday"),
    	@Result(property = "roleList",column = "id",
    		javaType = List.class,
    		many = @Many(select = "com.lagou.mapper.RoleMapper.findByUid"))
    	})
    	List findAllUserAndRole();}
    
    public interface RoleMapper {
    	@Select("select * from role r,user_role ur where r.id=ur.role_id and
    	ur.user_id=#{uid}")
    	List findByUid(int uid);
    }

    测试结果

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List all = mapper.findAllUserAndRole();
    for(User user : all){
    	System.out.println(user.getUsername());
    	List roleList = user.getRoleList();
    	for(Role role : roleList){
    		System.out.println(role);
    	}
    	System.out.println("----------------------------------");
    }

    +MyBatis

  • 相关阅读:
    leetcode:53. 最大子数组和
    VUE element-ui之form表单中input输入超过规定长度error提醒,并实时显示输入长度,可无限输入
    HTTP流量神器Goreplay核心源码详解
    企业管理 - 波司登战略管理解析
    为什么SQL预编译可以防止SQL注入攻击
    一加Nord N300 5G什么时候发布 一加Nord N300 5G配置如何
    35 LRU缓存
    [线程安全问题] 多线程到底可能会带来哪些风险?
    22/7/20
    docker版jxTMS使用指南:数据总线
  • 原文地址:https://blog.csdn.net/JHIII/article/details/126501027