手写mybatis缓存

目录

1.缓存篇

1.1 缓存介绍:

1.1 .1缓存是什么?

1.1.2 为什么要应用缓存? 

1.1.3 使用缓存有什么优势和劣势?

1.2 缓存模拟

1.2.1 缓存的基础构建

        ①缓存接口设计

          ②简易缓存实现

          ③ 基于FIFO(先进先出算法)设计缓存对象的淘汰策略,代码如下.

        ④ 基于LRU(最近最少访问)算法

        ⑤Caffeine缓存的实现


1.缓存篇

1.1 缓存介绍:

1.1 .1缓存是什么?

 
        缓存是内存中存在的一个对象,可以是Map.collection等类型,利用这个对象可以对某些数据对象进行存储.对于数据库而言,这个对象也可以看作是一个临时的数据库,里面包含着实际数据库中的部分数据.


1.1.2 为什么要应用缓存? 

        在不使用缓存时,用户每次用浏览器访问某网站获取某项服务时,网站必须从数据库中进行相应的数据增删改查的工作,流程如下

         用户的每一次请求都需要走一遍完整的过程,而当访问数量较多的时候,服务器会达到访问瓶颈,此时再进行访问时,该访问请求要么放入请求队列进行等候,要么作为无效请求.这样的结果就是系统效率低,用户体验差,造成用户流失......

        而如果使用缓存,则可以大幅度降低数据的访问量.例如浏览器的本地缓存,可以缓存用户访问过的网页的html.js,image等文件,当用户再次访问时,可以直接从本地客户端获取这些文件,不需要从服务端进行请求.   例如服务器的本地缓存和分布式缓存,都可以作为存储部分数据库数据的"小型临时数据库",当web服务器需要访问数据库获取数据时,则可以先从缓存中查询,有则直接返回结果,没有则再查询数据库获取结果(如果有分布式缓存还会查分布式缓存,之后再查实际数据库).

 

1.1.3 使用缓存有什么优势和劣势?

        使用缓存的优势总结来讲就是两点,①提高系统功能效率②减少数据库压力.

        使用缓存有优势就必然有劣势和缺点,上面提到,对于服务器缓存来讲,它就是一个"临时的小型数据库","小型"也意味着会存储部分数据,需要消耗空间资源,是一种空间换时间的做法(一般来讲是值得的),"临时"则意味着缓存的数据是经常变换更新的,并且由于缓存中只是一部分数据库的信息,这就可能会造成一些问题,这些问题大致可以分为三种,缓存穿透,缓存击穿,缓存雪崩.这三个问题此处不进行详述

1.2 缓存模拟

1.2.1 缓存的基础构建

        此处用HashMap作为缓存对象进行实现.

        ①缓存接口设计

public interface Cache {
    void putObject(Object key,Object value);
    Object getObject(Object key);
    Object removeObject(Object key);
    void clear();
    int size();
}

          ②简易缓存实现

public class PerpetualCache implements Cache{

    private HashMap<Object,Object> cache=new HashMap<Object,Object>();
    @Override
    public void putObject(Object key, Object value) {
        cache.put(key, value);
    }

    @Override
    public Object getObject(Object key) {
        return cache.get(key);
    }

    @Override
    public Object removeObject(Object key) {
        return cache.remove(key);
    }

    @Override
    public void clear() {
        cache.clear();
    }

    @Override
    public int size() {
        return cache.size();
    }

    @Override
    public String toString() {
        return "PerpetualCache{" +
                "cache=" + cache.toString() +
                '}';
    }
}

          ③ 基于FIFO(先进先出算法)设计缓存对象的淘汰策略,代码如下.

//FIFO-Cache实现
package com.cache;

import java.util.Deque;
import java.util.LinkedList;
import java.util.Queue;

public class FifoCache implements Cache{
    /**关联Cache对象-找到可以存储数据的基层对象*/
    private Cache cache;
    /**定义Cache的最大容量*/
    private int maxCap;
    /**通过队列记录key的添加顺序*/
    private Queue<Object> queue;

    public FifoCache(Cache cache, int maxCap) {
        this.cache = cache;
        this.maxCap = maxCap;
        this.queue=new LinkedList<Object>();
    }
    @Override
    public void putObject(Object key, Object value) {
        //1.记录key的顺序(加入队列)
        queue.add(key);

        //2.判断容器是否已满,满了则移除
        if(queue.size()>maxCap){
            Object firstKey = queue.remove();
            //从cache中清除指定key对应的对象
            cache.removeObject(firstKey);
        }
        //3.向cache添加数据
        cache.putObject(key, value);
    }
    @Override
    public Object getObject(Object key) {
        return cache.getObject(key);
    }

    @Override
    public Object removeObject(Object key) {
        //1.从Cache中移除对象
        Object object = cache.removeObject(key);
        //2.从队列移除key
        queue.remove(key);
        return object;
    }
    @Override
    public void clear() {
          cache.clear();
          queue.clear();
    }

    @Override
    public int size() {
        return cache.size();
    }

    @Override
    public String toString() {
        return "FifoCache{" +
                "cache=" + cache.toString() +
                ", maxCap=" + maxCap +
                ", queue=" + queue +
                '}';
    }

    public static void main(String[] args) {
        Cache cache=new FifoCache(new PerpetualCache(), 3);
       cache.putObject("胡桃", 1);
        cache.putObject("钟离", 2);
        cache.putObject("甘雨", 3);
        cache.putObject("影", 4);
        cache.putObject("凝光", 5);
        System.out.println(cache);
    }
}

        基于FIFO进行实现,依次插入胡桃,钟离,甘雨,影,凝光五组值,由于限制大小为3,所以map会将前面插入的胡桃和钟离移除,最终剩下甘雨,影,凝光.(因为HashMap是无序的,所以cache中的值与queue中的值的序列不同)

        ④ 基于LRU(最近最少访问)算法

public class LruCache implements Cache{
    private Cache cache;
    /**
     * 通过此属性记录要移除的数据对象
     */
    private Object eldestKey;
    private int maxcap;
    /**
     * 通过此 map 记录 key 的访问顺序
     */
    private Map<Object, Object> keyMap;

    @SuppressWarnings("serial")
    public LruCache(final Cache cache, int maxCap) {
        this.cache = cache;         //LinkedHashMap 可以记录 key 的添加顺序或者访问顺序
        this.maxcap=maxCap;
        this.keyMap = new LinkedHashMap<Object, Object>(maxCap, 0.75f, true) {//accessOrder                     // 此方法每次执行 keyMap 的 put 操作时调用
            @Override
            protected boolean removeEldestEntry(java.util.Map.Entry<Object, Object> eldest) {
                boolean isFull =cache.size()>maxcap;
                if (isFull) eldestKey = eldest.getKey();
                return isFull;
            }
        };
    }

    @Override
    public void putObject(Object key, Object value) {         // 存储数据对象
        cache.putObject(key, value);         // 记录 key 的访问顺序,假如已经满了,就要从 cache 中移除数据
        keyMap.put(key, key);// 此时会执行 keyMap 对象的 removeEldestEntry
          if(eldestKey!=null) {
              cache.removeObject(eldestKey);
              eldestKey = null;
    }

}

    @Override
    public Object getObject(Object key) {
        keyMap.get(key);// 记录 key 的访问顺序
        return cache.getObject(key);
    }

    @Override
    public Object removeObject(Object key) {
        return cache.removeObject(key);
    }

    @Override
    public void clear() {
        cache.clear();
        keyMap.clear();
    }

    @Override
    public int size() {
        return cache.size();
    }

    @Override
    public String toString() {
        return cache.toString();
    }

    public static void main(String[] args) {
        LruCache cache = new LruCache(new PerpetualCache(), 3);
        cache.putObject("胡桃", 1);
        cache.putObject("钟离", 2);
        cache.putObject("甘雨", 3);

        cache.getObject("钟离");
        cache.getObject("胡桃");

        cache.putObject("影", 4);
        cache.putObject("凝光", 5);

        System.out.println(cache);

    }

}

由于最后访问了"胡桃"和"钟离",所以在进行淘汰时,优先淘汰最后访问的数据.

        ⑤Caffeine缓存的实现

这种实现比较复杂,是在spring框架中实现的,需要引入额外的依赖

<dependencies>
    <!--SpringBoot依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.3.2.RELEASE</version>
    </dependency>
    <!--本地缓存caffeine-->
    <dependency>
        <groupId>com.github.ben-manes.caffeine</groupId>
        <artifactId>caffeine</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
</dependencies>

先写配置类

@EnableCaching
@Configuration
public class CacheConfig {
    @Bean
    public CaffeineCacheManager caffeineCacheManager(){
        //1.构建缓存对象
        Caffeine caffeine = Caffeine.newBuilder()
                .initialCapacity(1) //初始大小
                .maximumSize(3)  //最大大小
                .expireAfterWrite(1, TimeUnit.HOURS); //写入/更新之后1小时过期

        //构建缓存管理器对象
        CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
        caffeineCacheManager.setAllowNullValues(true);
        caffeineCacheManager.setCaffeine(caffeine);
        return caffeineCacheManager;
    }

}

控制层

@RestController
public class CaffeineController {
    @Autowired
    CaffeineCacheManager caffeineCacheManager;

    static int count=1;
    @Cacheable(cacheManager = "caffeineCacheManager", value = "caffeine")
    @GetMapping("/caffeine/{id}")
    public Object selectById(@PathVariable("id") String id) {

        System.out.println("模拟数据库获取数据"+count++);
        //返回的结果会自动存入caffeineCache中
        return  "结果"+count;
    }
}

启动项目,通过.http进行测试

执行测试

GET http://localhost:8080/caffeine/1
###
GET http://localhost:8080/caffeine/2
###
GET http://localhost:8080/caffeine/3
####
GET http://localhost:8080/caffeine/2
####
GET http://localhost:8080/caffeine/4
####
GET http://localhost:8080/caffeine/1
####

结果如下,在第二次执行GET http://localhost:8080/caffeine/1请求时,由于容量设置为3,所以之前的数据被移除,只能再重新从数据库中获取.实际的Caffeine由于数据量较少,无法测出准确的性能

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Mybatis 是一款优秀的持久层框架,它可以对 JDBC 进行封装,使得开发者可以通过 XML 文件或注解来配置 SQL 语句,从而避免了手写 JDBC 代码的繁琐和重复。下面,我将简单介绍如何手写一个简单版的 Mybatis。 1. 创建配置文件 在 resources 目录下创建一个 mybatis-config.xml 文件,用于配置 Mybatis 的各种参数。其中包括数据库连接信息、mapper 文件路径、缓存配置、插件等。以下是一个简单的配置文件示例: ``` <configuration> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> <mappers> <mapper resource="com/example/mapper/UserMapper.xml"/> </mappers> </configuration> ``` 2. 创建数据库连接池 在 Mybatis 中,我们可以通过数据源来获取数据库连接。下面是一个简单的数据源示例: ``` public class PooledDataSource { private static final String driver = "com.mysql.jdbc.Driver"; private static final String url = "jdbc:mysql://localhost:3306/mybatis"; private static final String username = "root"; private static final String password = "root"; private static final int initialSize = 5; private static final int maxActive = 10; private static final int maxIdle = 8; private static final int minIdle = 5; private static final long maxWait = 60000; private static DataSource dataSource; static { try { GenericObjectPoolConfig<Connection> poolConfig = new GenericObjectPoolConfig<>(); poolConfig.setMinIdle(minIdle); poolConfig.setMaxIdle(maxIdle); poolConfig.setMaxTotal(maxActive); poolConfig.setMaxWaitMillis(maxWait); ConnectionFactory connectionFactory = new DriverManagerConnectionFactory(url, username, password); PoolableConnectionFactory poolableConnectionFactory = new PoolableConnectionFactory(connectionFactory, null); poolableConnectionFactory.setValidationQuery("SELECT 1"); dataSource = new PoolingDataSource(poolableConnectionFactory.getPool()); } catch (Exception e) { e.printStackTrace(); } } public static DataSource getDataSource() { return dataSource; } } ``` 3. 创建 SqlSession SqlSession 是 Mybatis 中用于执行 SQL 语句的核心接口,它提供了各种方法来执行 SQL 语句、获取 Mapper 等。下面是一个简单的 SqlSession 实现: ``` public class DefaultSqlSession implements SqlSession { private final Configuration configuration; private final Executor executor; public DefaultSqlSession(Configuration configuration, Executor executor) { this.configuration = configuration; this.executor = executor; } @Override public <T> T selectOne(String statement, Object parameter) { return executor.query(statement, parameter); } @Override public <T> T getMapper(Class<T> type) { return configuration.getMapper(type, this); } } ``` 4. 创建 Executor Executor 是 Mybatis 中用于执行 SQL 语句的实现类,它提供了各种方法来执行 SQL 语句、获取结果等。下面是一个简单的 Executor 实现: ``` public class SimpleExecutor implements Executor { private final DataSource dataSource; public SimpleExecutor(DataSource dataSource) { this.dataSource = dataSource; } @Override public <T> T query(String statement, Object parameter) { Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { connection = dataSource.getConnection(); preparedStatement = connection.prepareStatement(statement); preparedStatement.setString(1, parameter.toString()); resultSet = preparedStatement.executeQuery(); if (resultSet.next()) { return (T) resultSet.getString(1); } } catch (SQLException e) { e.printStackTrace(); } finally { try { resultSet.close(); preparedStatement.close(); connection.close(); } catch (SQLException e) { e.printStackTrace(); } } return null; } } ``` 5. 创建 Mapper Mapper 是 Mybatis 中用于定义 SQL 语句的接口,它包含了各种方法来执行 SQL 语句、获取结果等。下面是一个简单的 Mapper 接口示例: ``` public interface UserMapper { @Select("SELECT * FROM user WHERE id = #{id}") User selectById(Integer id); } ``` 6. 创建 Configuration Configuration 是 Mybatis 中用于配置各种参数的类,它负责解析配置文件、创建 SqlSession 和 Mapper 等。下面是一个简单的 Configuration 实现: ``` public class Configuration { private final Properties properties; private final Map<Class<?>, MapperProxyFactory<?>> mapperProxyFactoryMap = new HashMap<>(); public Configuration(Properties properties) { this.properties = properties; } public <T> T getMapper(Class<T> type, SqlSession sqlSession) { MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) mapperProxyFactoryMap.get(type); if (mapperProxyFactory == null) { mapperProxyFactory = new MapperProxyFactory<>(type); mapperProxyFactoryMap.put(type, mapperProxyFactory); } return mapperProxyFactory.newInstance(sqlSession); } } ``` 7. 创建 MapperProxyFactory 和 MapperProxy MapperProxyFactory 是 Mybatis 中用于创建 MapperProxy 的工厂类,它负责创建 MapperProxy 并维护 Mapper 接口和对应的 MapperProxy。MapperProxy 是 Mybatis 中用于动态代理 Mapper 接口的类,它负责解析 Mapper 接口中定义的 SQL 语句,并将其转化为对应的 SQL 语句执行器。下面是一个简单的 MapperProxyFactory 和 MapperProxy 实现: ``` public class MapperProxyFactory<T> { private final Class<T> mapperInterface; public MapperProxyFactory(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } public T newInstance(SqlSession sqlSession) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, new MapperProxy(sqlSession, mapperInterface)); } } public class MapperProxy<T> implements InvocationHandler { private final SqlSession sqlSession; private final Class<T> mapperInterface; public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Select select = method.getAnnotation(Select.class); if (select != null) { String sql = select.value()[0]; return sqlSession.selectOne(sql, args[0]); } throw new RuntimeException("No such method: " + method.getName()); } } ``` 至此,我们就手写了一个简单版的 Mybatis,它包含了配置文件、数据库连接池、SqlSession、Executor、Mapper 等核心组件。虽然这个实现比较简单,但它足以帮助我们理解 Mybatis 的核心原理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值