项目结构
1.实体model模块
2.功能模块sb-mybatis
实际操作
1.引入依赖
org.mybatis.spring.boot mybatis-spring-boot-starter 2.2.2 com.github.pagehelper pagehelper 5.1.10 cn.hutool hutool-all mysql mysql-connector-java runtime org.projectlombok lombok true
1.自定义注解分页PageX
package com.example.annotation;
import java.lang.annotation.*;
//自定义注解
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PageX {
}
2.自定义封装SQL语句注解
Column列
package com.beiyou.annotion;
import java.lang.annotation.*;
@Target({ ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Column {
String value() default "";
}
EQ等于
package com.beiyou.annotion;
import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Table {
String value() default "";
}
NQ 不等于
package com.beiyou.annotion;
import java.lang.annotation.*;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NE {
String value() default "";
}
IN 条件in
package com.beiyou.annotion;
import java.lang.annotation.*;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface IN {
String value() default "";
}
Like 条件like
package com.beiyou.annotion;
import java.lang.annotation.*;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Like {
String value() default "";
}
PK 主键
package com.beiyou.annotion;
import java.lang.annotation.*;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PK {
String value() default "";
boolean autoIncrement() default true;
}
Table 表名
package com.beiyou.annotion;
import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Table {
String value() default "";
}
Ignore 忽略不封装
package com.beiyou.annotion;
import java.lang.annotation.*;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Ignore {
String value() default "";
}
3.实体
Range查询范围的类,SQL语句封装
package com.beiyou.common; import java.io.Serializable; //范围查询的类 public class Rangeimplements Serializable { private T left; private boolean leftEQ; private T right; private boolean rightEQ; public Range() { } public Range(T left, boolean leftEQ, T right, boolean rightEQ) { this.left = left; this.leftEQ = leftEQ; this.right = right; this.rightEQ = rightEQ; } public T getLeft() { return left; } public void setLeft(T left) { this.left = left; } public boolean isLeftEQ() { return leftEQ; } public void setLeftEQ(boolean leftEQ) { this.leftEQ = leftEQ; } public T getRight() { return right; } public void setRight(T right) { this.right = right; } public boolean isRightEQ() { return rightEQ; } public void setRightEQ(boolean rightEQ) { this.rightEQ = rightEQ; } public static Range.RangeBuilder builder() { return new Range.RangeBuilder (); } public static class RangeBuilder implements Serializable { private T left; private boolean leftEQ = true; private T right; private boolean rightEQ = true; RangeBuilder() { } public Range.RangeBuilder left(T left) { this.left = left; return this; } public Range.RangeBuilder leftEQ(boolean leftEQ) { this.leftEQ = leftEQ; return this; } public Range.RangeBuilder right(T right) { this.right = right; return this; } public Range.RangeBuilder rightEQ(boolean rightEQ) { this.rightEQ = rightEQ; return this; } public Range build() { return new Range (this.left, this.leftEQ, this.right, this.rightEQ); } } }
HttpResponse统一返回值
package com.beiyou.model;
import lombok.Data;
import java.io.Serializable;
@Data
//{code:0,message:"成功",data:"ok"}
//{code:502,message:"服务器内部错误",data:null}
//T泛型,可以为多种类型
public class HttpResponse implements Serializable {
//返回码(公司内部拟定),
// 0 代码正常,
// 非0代表错误 : 1,10001,500 。
private int code;
//返回信息,错误信息,
// 0 : "成功",
// 非0 : 的时候就是具体的错误信息.
private String message;
private T data;
}
测试分页的实体
package com.beiyou.model;
import com.beiyou.annotion.*;
import com.beiyou.common.Range;
import lombok.Builder;
import lombok.Data;
@Data
@Table("orderMaster")
@Builder
public class OrderMasterQuery extends AbstractQuery {
@EQ
private Long id;
@IN
@Column("id")
private Long[] ids;
@Like
private String receiver;
@NE
private String phoneNumber;
@EQ
@Column("phoneNumber")
private String phoneNumberEQ;
@Column("id")
private Range idRange;
@Ignore
private String sourceFrom;
}
分页当前页,当前页数量
package com.beiyou.model;
import java.io.Serializable;
public abstract class AbstractQuery implements Serializable {
private Integer pageIndex;
private Integer pageSize;
private String orderByBlock; // " a desc,b asc,c asc"
public Integer getPageIndex() {
return pageIndex;
}
public void setPageIndex(Integer pageIndex) {
this.pageIndex = pageIndex;
}
public Integer getPageSize() {
return pageSize;
}
public void setPageSize(Integer pageSize) {
this.pageSize = pageSize;
}
public String getOrderByBlock() {
return orderByBlock;
}
public void setOrderByBlock(String orderByBlock) {
this.orderByBlock = orderByBlock;
}
}
4.配置分页插件
package com.example.config;
import com.github.pagehelper.PageInterceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class MybatisConfig {
@Bean
public SqlSessionFactory getSqlSessionFactory(DataSource ds) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
//设置数据源
factory.setDataSource(ds);
//把分页插件设置到SqlSessionFactory插件库
factory.setPlugins(new PageInterceptor());
//返回具体实例对象SqlSessionFactory
// SqlSessionFactoryBean,Bean在后的特殊Bean需要调用getObject()返回Bean
SqlSessionFactory object = factory.getObject();
return object;
}
}
5.配置redis序列化,解决乱码
package com.example.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean(name = "redisTemplate")
public RedisTemplate
6.配置application.properties
#连接数据库 spring.datasource.url=jdbc:mysql://localhost:3303/mall?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC spring.datasource.username=root spring.datasource.password=@root spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver logging.level.com.example=debug #配置自定义注解 pointcut.property=@annotation(com.example.annotation.PageX) #redis配置 #redis服务器地址 spring.redis.host=192.168.43.1 #端口号 spring.redis.port=6379 #redis服务器密码默认为空 #spring.redis.password= #redis最大连接数 spring.redis.lettuce.pool.max-active=16 #连接池最大阻塞等待时间,(使用负数表示没有限制) spring.redis.lettuce.pool.max-wait=-1 #连接池的最大空闲时间,默认为8 spring.redis.lettuce.pool.max-idle=8 #连接池的最小空闲时间,默认为8 spring.redis.lettuce.pool.min-idle=0 #连接超时时间 spring.redis.timeout=30000
7.编写分页拦截器
package com.example.interceptor;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.beiyou.model.AbstractQuery;
import com.github.pagehelper.PageHelper;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import java.lang.reflect.Method;
@Slf4j
public class MapperInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
//当前拦截的方法
Method method = invocation.getMethod();
//当前拦截方法的参数
Object[] arguments = invocation.getArguments();
//获取第一个参数,因为我们Mapper对应的方法只有一个参数
Object arg0 = arguments[0];
// 判断是否是Query对象
if (arg0 instanceof AbstractQuery) {
//强转 AbstractQuery, 目的是获取参数,是否要分页
AbstractQuery query = (AbstractQuery) arg0;
Integer pageIndex = query.getPageIndex(); //参数,第几页。
Integer pageSize = query.getPageSize();// 参数,每页多少条。
//判断不为空
if (ObjectUtil.isNotEmpty(pageIndex) && ObjectUtil.isNotEmpty(pageSize)) {
//启用分页,查询第pageIndex页,每页pageSize条
PageHelper.startPage(pageIndex, pageSize);
}
}
//执行这个方法
Object value = invocation.proceed();
log.info("method:{},args:{},value:{}", method.getName(), JSONUtil.toJsonStr(arguments), value);
return value;
}
}
8.注册拦截器
package com.example.config;
import com.example.interceptor.MapperInterceptor;
import org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AopConfig {
@Value("${pointcut.property}")
private String expression;
@Bean
public AspectJExpressionPointcutAdvisor aspectJExpressionPointcutAdvisor() {
AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor();
advisor.setExpression(expression);
//注入要拦截的bean
advisor.setAdvice(new MapperInterceptor());
return advisor;
}
}
9.集成分页做统一返回值
package com.example.advice;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.beiyou.model.HttpResponse;
import com.github.pagehelper.Page;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import javax.servlet.http.HttpServletRequest;
//basePackages 设置统一拦截基础包名
@RestControllerAdvice(basePackages = {"com.example.controller"})
public class MyResponseBodyAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;//统一做拦截
}
//处理返回异常
@ExceptionHandler(value = Exception.class)
public Object defaultErrorHandler(HttpServletRequest req, Exception ex) {
HttpResponse httpResponse = new HttpResponse<>();
httpResponse.setCode(1);
httpResponse.setMessage(ex.getMessage());
return httpResponse;
}
@Override
public Object beforeBodyWrite(Object body,
MethodParameter returnType,
MediaType selectedContentType,
Class selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
HttpResponse httpResponse = new HttpResponse<>();
String message = StrUtil.EMPTY;//初始值空值
httpResponse.setCode(0);
httpResponse.setMessage(message);
//body是否是Page类型
if (body instanceof Page) {
Page page = (Page) body;
int pages = page.getPages();//总页数
long total = page.getTotal();//总条数
//hutool通过链式构造,创建Dict对象,同时可以按照Map的方式使用。
Dict dic = Dict.create()
.set("total", total)
.set("pages", pages)
.set("items", page);
httpResponse.setData(dic);
} else {
httpResponse.setData(body);
}
//处理返回字符串
if (selectedConverterType == StringHttpMessageConverter.class) {
return JSONUtil.toJsonStr(httpResponse);
} else {
return httpResponse;
}
}
}
10.封装SQL语句
package com.example.util;
import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.beiyou.annotion.*;
import com.beiyou.common.Range;
import com.beiyou.model.AbstractQuery;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
public class SqlGen {
public static final String select = "select";
public static final String insert = "insert";
public static final String update = "update";
public static HashMap selectMap = new HashMap<>();
private static final HashSet pageFileds = new HashSet() {
{
add("pageIndex");
add("pageSize");
add("orderByBlock");
}
};
//构建update语句
// update 表名 set a = #{a},b = #{b} where id = #{id}
public String update(Object model) {
Class> modelClass = model.getClass();
StringBuilder sb = new StringBuilder();
sb.append(" ");
System.out.println(sb.toString());
return sb.toString().trim();
}
//构建insert语句
public String insert(Object model) {
//xml :
// insert into A (a,b,c) values (#{a},#{b},#{c});
Class> modelClass = model.getClass();
StringBuilder sb = new StringBuilder();
sb.append(" ");
System.out.println(sb.toString());
return sb.toString().trim();
}
//构建查询语句
public String select(AbstractQuery query) {
String queryClassName = query.getClass().getName();
//如果缓存里面有对应的sql语句,直接return;
if (selectMap.containsKey(queryClassName)) {
return selectMap.get(queryClassName);
}
String modelClassName = StrUtil.sub(queryClassName, 0, queryClassName.length() - 5);
String tableName = "";
Class> modelClass = ClassUtil.loadClass(modelClassName); // OrderMaster.class
//获取表名, 可以通过 orderMaster上注解,也可以通过OrderMasterQuery上注解
Table table = AnnotationUtil.getAnnotation(modelClass, Table.class);
if (table != null) {
tableName = table.value();
} else {
table = AnnotationUtil.getAnnotation(query.getClass(), Table.class);
if (table != null) {
tableName = table.value();
}
}
Assert.notNull(tableName, "{} 没有设置具体表名", modelClassName);
StringBuilder sb = new StringBuilder();
sb.append(" ");
String sql = sb.toString().trim();
//如果缓存没有,这里直接添加
selectMap.putIfAbsent(queryClassName, sql);
return sql;
}
}
11.mapper层数据库操作
package com.example.mapper;
import com.beiyou.model.OrderMaster;
import com.beiyou.model.OrderMasterQuery;
import com.example.annotation.PageX;
import com.example.util.SqlGen;
import org.apache.ibatis.annotations.*;
import java.util.List;
@Mapper
public interface OrderMaterMapper {
//自定义的分页拦截器注解
@PageX
@SelectProvider(type = SqlGen.class, method = SqlGen.select)
List queryByOrderMasterQuery(OrderMasterQuery query);
//插入orderMaster表
@SelectKey(keyColumn = "id", keyProperty = "id", statement = "select last_insert_id()", before = false, resultType = Long.class)
// @SelectKey(keyColumn = "id", keyProperty = "id", statement = "select max(id)+1 as id from ordermaster", before = true,
// resultType = Long.class)
@InsertProvider(type = SqlGen.class, method = SqlGen.insert)
Integer insertOrderMaster(OrderMaster orderMaster);
@UpdateProvider(type = SqlGen.class, method = SqlGen.update)
Integer updateOrderMaster(OrderMaster orderMaster);
}
12.测试的controller
package com.example.controller;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import com.beiyou.model.OrderMaster;
import com.beiyou.model.OrderMasterQuery;
import com.example.mapper.OrderMaterMapper;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Slf4j
@RestController
public class DemoController {
@Resource
private OrderMaterMapper orderMaterMapper;
//测试 update
@GetMapping("/test/update")
public Integer testUpdate() {
OrderMasterQuery query = OrderMasterQuery.builder().id(7L).build();
List orderMasters = orderMaterMapper.queryByOrderMasterQuery(query);
OrderMaster one = orderMasters.get(0);
one.setAddress1(one.getAddress1() + "--up");
Integer ret = orderMaterMapper.updateOrderMaster(one);
return ret;
}
//redis的使用
@Resource(name = "redisTemplate")
private ValueOperations valueOps;
public final String orderPre = "order#";
//测试 insert
@PostMapping("/test/insert")
public Integer testInser(@RequestBody OrderMaster orderMaster) {
//数据库添加
Integer ret = orderMaterMapper.insertOrderMaster(orderMaster);
//redis添加
String key = orderPre + orderMaster.getId();
valueOps.set(key, orderMaster);
return ret;
}
// 前端调用测试
@PostMapping("/test/sqlgen2")
public List testSqlGen2(@RequestBody OrderMasterQuery query) {
List orderMasters = orderMaterMapper.queryByOrderMasterQuery(query);
return orderMasters;
}
//使用我们的sqlGen生成的xml
@PostMapping("/test/sqlgen")
public List testSqlGen() {
// 测试 =
// OrderMasterQuery query = OrderMasterQuery.builder().id(8L).build();
// 测试 in
// Long[] ids = new Long[]{5L,6L,7L};
//OrderMasterQuery query = OrderMasterQuery.builder().ids(ids).build();
// 测试范围查询
// Range idRange = Range.builder().right(10L).rightEQ(true).build();
// OrderMasterQuery query = OrderMasterQuery.builder().idRange(idRange).build();
// query.setPageIndex(1);
// query.setPageSize(3);
// 测试 like
// OrderMasterQuery query = OrderMasterQuery.builder().receiver("星").build();
// List orderMasters = orderMaterMapper.queryByOrderMasterQuery(query);
// 测试 不等于
OrderMasterQuery query = OrderMasterQuery.builder().phoneNumber("18676644557").build();
List orderMasters = orderMaterMapper.queryByOrderMasterQuery(query);
return orderMasters;
}
//redis的使用
//使用分页插件 前端传参定第几页,几条
@PostMapping("/test/QueryByOrderMasterQuery")
public List testByOrderMasterQuery(@RequestBody OrderMasterQuery query) {
//不经过数据库查
//retList放的是在redis里面的
List retList = new ArrayList<>();
boolean b = ObjectUtil.isNotEmpty(query.getId());
//对通过ID单个查询
if (b == true) {
//key = order#123
OrderMaster orderMaster = valueOps.get(orderPre + query.getId());
if (orderMaster != null) {
retList.add(orderMaster);
log.debug("直接从redis获取,没有经过数据库");
return retList;
}
}
//针对ids 多个查询
//ids: [58,59,60,61,87]
//redis: [61,87]
//[58,59,60]不在
Long[] ids = query.getIds();
//notInRedisIds放的是不在redis里面的
ArrayList notInRedisIds = new ArrayList<>();
for (Long id : ids) {
//组装key
OrderMaster orderMaster = valueOps.get(orderPre + id);
if (orderMaster != null) {
retList.add(orderMaster);
} else {
//如过redis没有查到,我们就把ID放进 notInRedisIds;
notInRedisIds.add(id);
}
}
if (ObjectUtil.isEmpty(notInRedisIds)) { //代表都在redis里面
return retList;
}
query.setIds(ArrayUtil.toArray(notInRedisIds, Long.class));
log.debug("筛选后的ids:{}", query.getIds());
//通过数据库查找
List orderMasters = orderMaterMapper.queryByOrderMasterQuery(query);
log.debug("经过数据库");
//如过通过数据库查找过来的数据,写到redis
for (OrderMaster orderMaster : orderMasters) {
valueOps.set(orderPre + orderMaster.getId(), orderMaster, 7, TimeUnit.DAYS);
log.debug("通过数据库查得:{}", orderMaster.getId());
}
return orderMasters;
}
}



