下面是在redis存储数据的截图:
上面左侧是redis仓库的存储目录,这些目录由redis封装模块定定义。这些数据是存储在计算机内存上,所以读取的速度非常快,相比与数据库的读取速度要快几十甚至几百倍,因此redis通常被用作数据库、缓存和消息代理。redis支持多种类型的数据结构,如字符串(strings)、列表(lists)、集合(sets)、有序集合(sorted sets)、散列(hashes)、位图(bitmaps)、超日志(hyperloglogs)和地理空间索引(geospatial indexes)。
虽然redis存储数据在内存上,但是也可以做数据持久化,即把数据存储到硬盘上,下面会介绍几种数据持久化方式。
用过Node框架的小伙伴肯定少不了与ORM框架打交道,ORM框架虽然免去了写SQL语句的繁琐但是也要熟悉ORM本身的语法学习成本也较高,MyBatis通常被认为是一个持久层框架,而不是传统意义上的完整ORM(对象关系映射)框架。它更侧重于SQL和对象之间的映射,而不是提供完全的对象数据库抽象。
若依系统采用的是MyBatis,下文梳理MyBatis在若依框架中的使用与封装。
import org.springframework.data.redis.core.RedisTemplate;
public class RedisService
{
@Autowired
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit)
{
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit)
{
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key)
{
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public boolean deleteObject(final Collection collection)
{
return redisTemplate.delete(collection) > 0;
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long setCacheList(final String key, final List<T> dataList)
{
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key)
{
return redisTemplate.opsForList().range(key, 0, -1);
}
}
上面是一个简化的封装方法,这里有一个特殊依赖 RedisTemplate,主要是理解什么是RedisTemplate,有哪些作用:
实际上RedisTemplate来自于 org.springframework.data.redis.core.RedisTemplate包, 他是 Spring Data Redis 提供的一个中心类,简化了 Redis 数据访问代码。
以下是 RedisTemplate 的一些关键特性:
在上述的RedisConfig.java文件中自定义了RedisTemplate的一些默认配置:
@Configuration
@EnableCaching
@AutoConfigureBefore(RedisAutoConfiguration.class)
public class RedisConfig extends CachingConfigurerSupport
{
@Bean
@SuppressWarnings(value = { "unchecked", "rawtypes" })
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
{
//调用RedisTemplate的构造函数来创建一个新的RedisTemplate实例
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);// 创建和管理与Redis服务器的连接
// Object.class:这个参数指示了序列化器处理的对象类型。在这里使用Object.class暗示序列化器可以用于任意类型的Java对象。
FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
// Hash的key也采用StringRedisSerializer的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
// 确保所需属性被设置后能正确地初始化RedisTemplate
template.afterPropertiesSet();
return template;
}
}
@AutoConfigureBefore注解标明RedisConfig 会优先RedisAutoConfiguration加载,以确保用户自定义的配置会覆盖自动配置项。
在redisTemplate方法中:
redisService.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
SET captcha_codes:834d1022f42f467688ceb31bde1613 "25"
下面所列出的代码和截图都来自于ruoyi-system,其中的示例代码来自于SysUserMapper.xml文件。
使用xml文件配置业务逻辑,如若依中的mapper文件:
将SQL语句放在XML映射文件中,而不是注解里,以保证SQL的清晰性和可维护性。
举例
MyBatis的动态SQL是指可以根据传入的参数动态生成SQL语句的一种机制。通过使用MyBatis提供的各种XML标签,你可以在映射文件中编写能够根据不同条件自适应改变的SQL。这些标签包括:
<if>:根据条件判断是否包含某个SQL片段。
, , :类似于Java中的switch语句,根据不同的条件执行不同的SQL片段。
, , <set>:用于动态地添加或去除SQL语句的前缀或后缀,如WHERE、SET关键字或逗号。
<foreach>:对集合进行遍历,常用于构建IN查询。
:允许创建一个变量并将其绑定到当前上下文,可用于复杂的表达式。
比如下面的简单例子:
<select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult">
select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader from sys_user u
left join sys_dept d on u.dept_id = d.dept_id
where u.del_flag = '0'
<if test="userId != null and userId != 0">
AND u.user_id = #{userId}
if>
<if test="userName != null and userName != ''">
AND u.user_name like concat('%', #{userName}, '%')
if>
<if test="status != null and status != ''">
AND u.status = #{status}
if>
<if test="phonenumber != null and phonenumber != ''">
AND u.phonenumber like concat('%', #{phonenumber}, '%')
if>
<if test="params.beginTime != null and params.beginTime != ''">
AND date_format(u.create_time,'%y%m%d') >= date_format(#{params.beginTime},'%y%m%d')
if>
<if test="params.endTime != null and params.endTime != ''">
AND date_format(u.create_time,'%y%m%d') <= date_format(#{params.endTime},'%y%m%d')
if>
<if test="deptId != null and deptId != 0">
AND (u.dept_id = #{deptId} OR u.dept_id IN ( SELECT t.dept_id FROM sys_dept t WHERE find_in_set(#{deptId}, ancestors) ))
if>
${params.dataScope}
select>
上述例子中条件判断语句如果存在userId ,userName ,status ,phonenumber ,beginTime ,endTime ,deptId 则分别在Sql语句中拼接对应的查询条件。
语义解析:
<select id="selectUserList" ...>:定义了一个ID为selectUserList的查询操作,这个ID在Mapper接口中被引用。
parameterType="SysUser":指明传入参数的类型是SysUser类。
resultMap="SysUserResult":指定了返回结果的映射规则。
SQL查询语句:
至于什么是left join 或者如何写sql后面会单独找机会分享。
在MyBatis中,resultMap是一种高级的结果映射机制,,resultMap提供了更多的灵活性和控制能力,特别是当处理复杂的关联或嵌套结果时。
比如下面的resultMap:
<resultMap type="SysUser" id="SysUserResult">
<id property="userId" column="user_id" />
<result property="deptId" column="dept_id" />
<result property="userName" column="user_name" />
<result property="nickName" column="nick_name" />
<result property="email" column="email" />
<result property="phonenumber" column="phonenumber" />
<result property="sex" column="sex" />
<result property="avatar" column="avatar" />
<result property="password" column="password" />
<result property="status" column="status" />
<result property="delFlag" column="del_flag" />
<result property="loginIp" column="login_ip" />
<result property="loginDate" column="login_date" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
<association property="dept" javaType="SysDept" resultMap="deptResult" />
<collection property="roles" javaType="java.util.List" resultMap="RoleResult" />
resultMap>
在举例动态SQL的时候里面的*resultMap=“SysUserResult”*就是指的上面的结构。
下面看一下接口请求时候返回的数据:
resultMap解析:
PageHelper的使用请参考若依的实现,这里简明说一下使用方法:
PageUtils类中定义了两个静态方法:startPage和clearPage。
startPage() 方法
此方法将根据传递给TableSupport.buildPageRequest()的请求参数来设置分页信息。
/**
* 获取用户列表
*/
@RequiresPermissions("system:user:list")
@GetMapping("/list")
public TableDataInfo list(SysUser user)
{
startPage(); // 会自动对下面的查询做分页处理
List<SysUser> list = userService.selectUserList(user);
return getDataTable(list);
}
N+1问题简单的理解就是如何最大程度的减少Sql查询次数,尤其是在做关联查询的时候。
其实在本例子中的resultMap 中已经解决了这个问题:
<association property="dept" javaType="SysDept" resultMap="deptResult" />
<collection property="roles" javaType="java.util.List" resultMap="RoleResult" />
association 用来处理一对一的关系,在定义时需要指明属性名(property)、Java类型(javaType)以及映射规则(resultMap 或 column + select)。通过 association,可以将查询的结果集中的一部分列映射到一个关联的对象属性中。
上面表示有一个名为 dept 的属性,它是一个 SysDept 类型的对象。resultMap=“deptResult” 指出了如何将 sys_dept 表的数据映射到 SysDept 对象中。必须已经在某处定义了一个 ID 为 deptResult 的 resultMap。
collection 用来处理一对多的关系,也就是一个属性对应多个对象构成的列表。同样,collection定义时需要指明属性名、Java类型以及相关联的 resultMap。
上面表示有一个名为 roles 的属性,它是一个 List 类型,存放的是多个 角色 对象。resultMap=“roleResult” 指出了如何将 sys_role 表的数据映射到角色对象列表中。同样地,必须存在一个 ID 为 roleResult 的 resultMap 来描述角色对象的映射方式。
上面的语句可以理解为这样的需要:
有一个用户类 SysUser,它与一个部门类 SysDept 有一个一对一关系,同时与角色类 Role 存在一对多关系。
上班期间码这么多字太离谱了