您的位置:首页 > 房产 > 家装 > 潍坊专业企业营销策划有哪些_西双版纳傣族自治州_广州seo黑帽培训_搜索引擎入口大全

潍坊专业企业营销策划有哪些_西双版纳傣族自治州_广州seo黑帽培训_搜索引擎入口大全

2025/1/11 5:42:40 来源:https://blog.csdn.net/qq_29166327/article/details/143474565  浏览:    关键词:潍坊专业企业营销策划有哪些_西双版纳傣族自治州_广州seo黑帽培训_搜索引擎入口大全
潍坊专业企业营销策划有哪些_西双版纳傣族自治州_广州seo黑帽培训_搜索引擎入口大全

9561efe1c6f52fe246f36b171e59724c.jpeg

背景

Mybatis是一个半自动化的ORM(对象关系映射)框架,它通过XML或注解的方式将Java对象和SQL语句进行映射,从而实现对数据库的增删改查操作。

Mybatis ORM简介

  • 基本概念:Mybatis简化了数据访问层的开发,通过映射配置文件或注解将Java对象与数据库表进行映射,实现了对象关系映射。

  • 优点:Mybatis提供了灵活的SQL编写方式,支持动态SQL,易于与Spring等框架集成,同时减少了代码量,提高了开发效率。

  • 缺点:相比于全自动化的ORM框架如Hibernate,Mybatis需要编写更多的SQL语句,对开发人员的SQL编写能力有一定要求

Mybatis ORM的使用过程

  • 创建SqlSessionFactory:可以通过配置文件或直接编码来创建SqlSessionFactory。

  • 使用SqlSession进行数据库操作:通过SqlSession执行SQL语句,获取结果,并映射到Java对象。

d6a743c38e696db18a1049bf1cc17a54.png

引入依赖

<!-- 整合mybatis相关依赖 -->
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId>
</dependency><!-- 数据库驱动(例如 MySQL) -->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
</dependency>

数据库配置

数据库表初始化

CREATE TABLE `user` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',`name` varchar(255) NOT NULL DEFAULT '' COMMENT '账号',`age` int(2) NOT NULL DEFAULT 0 COMMENT '年龄',`email` varchar(255) NOT NULL DEFAULT '' COMMENT '邮箱',`tenant_id` bigint(20) NOT NULL DEFAULT 1 COMMENT '租户id,1表示默认租户',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';

xml文件映射sql:UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bryant.mapper.UserMapper"><resultMap id="BaseResultMap" type="com.bryant.model.UserDetail"><id column="id" property="id" jdbcType="INTEGER"/><result column="name" property="name" jdbcType="VARCHAR"></result><result column="age" property="age" jdbcType="INTEGER"></result><result column="email" property="email" jdbcType="VARCHAR"></result><result column="tenant_id" property="tenantId" jdbcType="BIGINT"></result></resultMap><sql id="base_columns">`id`, `name`, age, email, tenant_id</sql><insert id="insert" parameterType="map" keyColumn="id">insertinto user(id, name, age, email, tenant_id)values(#{record.id,jdbcType=INTEGER},#{record.name,jdbcType=VARCHAR},#{record.age,jdbcType=INTEGER},#{record.email,jdbcType=VARCHAR},#{record.tenantId,jdbcType=BIGINT})</insert><update id="updateById" parameterType="map">update userset `age` = #{record.age},`name` = #{record.name},`email` = #{record.email}where id = #{record.id}</update><delete id="deleteById">delete from userwhere id = #{id}</delete><select id="getById" resultMap="BaseResultMap">select<include refid="base_columns"/>from userwhere id = #{id}and tenant_id = #{tenantId}</select></mapper>

应用配置

启动类

//...其他注解
// mapper扫描的包路径
@MapperScan("com.bryant.mapper")
@SpringBootApplication
public class UserServer {public static void main(String[] args) {SpringApplication.run(UserServer.class, args);}}

注意标黄的代码段@MapperScan,是对包路径的扫描。

Mybatis配置

/*** Full(proxyBeanMethods = true) :proxyBeanMethods参数设置为true时即为:Full 全模式。该模式下注入容器中的同一个组件无论被取出多少次都是同一个bean实例,即单实例对象,在该模式下SpringBoot每次启动都会判断检查容器中是否存在该组件* Lite(proxyBeanMethods = false) :proxyBeanMethods参数设置为false时即为:Lite 轻量级模式。该模式下注入容器中的同一个组件无论被取出多少次都是不同的bean实例,即多实例对象,在该模式下SpringBoot每次启动会跳过检查容器中是否存在该组件*/
@Configuration(proxyBeanMethods = true)
@ConditionalOnProperty(prefix = "users.mybatis.custom", name = "interceptor", havingValue = "true")
@Slf4j
public class MybatisConfig implements InitializingBean {@Autowiredprivate List<SqlSessionFactory> sqlSessionFactorys;@Bean@Order(1)public TenantIdInjectInterceptor mybatisInterceptor() {log.info("TenantIdInjectInterceptor interceptor init...");return new TenantIdInjectInterceptor();}/*** @Order(0) 是为了保证 TenantIdInjectInterceptor拦截器先于sqlMonitorInterceptor拦截器执行* @return*/@Bean@Order(0)public SqlMonitorInterceptor sqlMonitorInterceptor() {log.info("SqlMonitorInterceptor interceptor init...");return new SqlMonitorInterceptor();}@Overridepublic void afterPropertiesSet() throws Exception {}}

Mybatis插件配置

SQL租户ID检测插件-TenantIdInjectInterceptor

可以参考上2篇文章:Mybatis插件-租户ID的注入&拦截应用、Mybatis链路分析:JDK动态代理和责任链模式的应用

/*** 拦截器做了2个事情* - 1.打印原sql* - 2.注入自定义参数** Mybatis 插件的执行顺序有两种:* 1、不同拦截对象执行顺序,如下:*     Executor` -> `StatementHandler` -> `ParameterHandler` -> `ResultSetHandler`* 2、拦截相同对象执行顺序,取决于 mybatis-config.xml 中 <plugin> 配置顺序,越靠后,优先级越高。* 3、拦截相同对象执行顺序,如果是使用的配置类加载,则取决于配置类的加载顺序,加载顺序,越靠后,优先级越高;*/
@Slf4j
@Intercepts(value = {@Signature(type = Executor.class, method = "update", args ={MappedStatement.class, Object.class}),@Signature(type = Executor.class, method = "query", args ={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),@Signature(type = Executor.class, method = "query", args ={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
})
public class TenantIdInjectInterceptor implements Interceptor {//用于匹配字符串中的tenant_id关键字,后面可以跟任意数量的空格,然后是等号=,再后面可以跟任意数量的空格,最后是一个问号?。// 这个正则表达式使用了Pattern.CASE_INSENSITIVE标志,表示匹配时不区分大小写。// 匹配问号,因为问号在正则表达式中有特殊含义(表示前面的字符出现0次或1次),所以需要用两个反斜杠进行转义private static final Pattern p = Pattern.compile("tenant_id(\\s+)?=(\\s+)?\\?", Pattern.CASE_INSENSITIVE);private static final String SQL_IGNORED_CHARACTOR = "[\\t\\n\\r]";/*** 核心逻辑在intercept方法,内部实现 sql 获取,参数解析,耗时统计* @param invocation* @return* @throws Throwable*/@Overridepublic Object intercept(Invocation invocation) throws Throwable {log.info("TenantIdInjectInterceptor interceptor start...");Object[] args = invocation.getArgs();MappedStatement mappedStatement = (MappedStatement) args[0];String namespace = mappedStatement.getId();String className = namespace.substring(0, namespace.lastIndexOf("."));String methodName = mappedStatement.getId().substring(mappedStatement.getId().lastIndexOf(".") + 1);Class<?> c = Class.forName(className);//如果Class有注解指明要忽略本次拦截,则放弃拦截if (c.isAnnotationPresent(TenantIdInjectConfig.class) && c.getAnnotation(TenantIdInjectConfig.class).ignore()) {return invocation.proceed();}Method[] ms = c.getMethods();Method method = Arrays.stream(ms).filter(m -> m.getName().equals(methodName)).findAny().get();//如果method 有注解指明要忽略本次拦截,则放弃拦截if (method.isAnnotationPresent(TenantIdInjectConfig.class) && method.getAnnotation(TenantIdInjectConfig.class).ignore()) {return invocation.proceed();}//判断SQL中是否存在tenant_id 字段,如果存在,认为已经考虑多租户的情况,否则将SQL拦截下来BoundSql boundSql = mappedStatement.getSqlSource().getBoundSql(args[1]);String sql = boundSql.getSql().toLowerCase(Locale.CHINA).replaceAll(SQL_IGNORED_CHARACTOR, " ");boolean sqlWithTenantIdParam;if (mappedStatement.getSqlCommandType().equals(SqlCommandType.INSERT)) {//insert语句只判断是否有tenant_idsqlWithTenantIdParam = sql.contains("tenant_id");} else {//其他语句判断是否有tenant_id=?sqlWithTenantIdParam = p.matcher(sql).find();}if (!sqlWithTenantIdParam) {log.error("缺少对多租户的支持,tenant_id 字段缺失,sql:{}", sql);throw new RuntimeException("缺少对多租户的支持,tenant_id字段缺失");}//这里使用默认的租户id=1String defaultTenantId = "1";Map map;if (args[1] == null) {//没有参数列表map = new MapperMethod.ParamMap<>();} else if (!(args[1] instanceof MapperMethod.ParamMap)) {//单参数Map tempMap = new MapperMethod.ParamMap<>();Parameter[] parameters = method.getParameters();Parameter param = parameters[0];//第一个参数获取@Param注解,然后获取值if (param.isAnnotationPresent(Param.class)) {String paramName = param.getAnnotation(Param.class).value();tempMap.put(paramName, args[1]);} else if (checkTypeType(param)) {//如果是基础类型抛出异常tempMap.put(param.getName(), args[1]);} else {//如果没有指定@Param,将单参数的属性单独拎出来处理Object arg = args[1];Field[] fields = arg.getClass().getDeclaredFields();for (Field field : fields) {if (!Modifier.isStatic(field.getModifiers())) {if (!field.isAccessible()) {field.setAccessible(true);}tempMap.put(field.getName(), field.get(arg));}}}args[1] = tempMap;}//如果是多参数列表时直接转map即可map = (Map) args[1];if (!StringUtils.isBlank(defaultTenantId)) {map.put("tenantId", Long.parseLong(defaultTenantId));}return invocation.proceed();}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}private boolean checkTypeType(Object object) {if (object.getClass().isPrimitive()|| object instanceof String|| object instanceof Boolean|| object instanceof Double|| object instanceof Float|| object instanceof Long|| object instanceof Integer|| object instanceof Byte|| object instanceof Short) {return true;} else {return false;}}
}

SQL监控插件-SqlMonitorInterceptor

@Slf4j
@Intercepts(value = {@Signature(type = Executor.class, method = "update", args ={MappedStatement.class, Object.class}),@Signature(type = Executor.class, method = "query", args ={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),@Signature(type = Executor.class, method = "query", args ={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
})
public class SqlMonitorInterceptor implements Interceptor {private static final String SQL_IGNORED_CHARACTOR = "[\\t\\n\\r]";@Overridepublic Object intercept(Invocation invocation) throws Throwable {log.info("SqlMonitorInterceptor interceptor start...");Object[] args = invocation.getArgs();MappedStatement mappedStatement = (MappedStatement) args[0];BoundSql boundSql = mappedStatement.getSqlSource().getBoundSql(args[1]);// 1.打印原sqllog.info("SqlMonitorInterceptor original sql: {}", boundSql.getSql().toLowerCase(Locale.CHINA).replace(SQL_IGNORED_CHARACTOR, " "));// BoundSql就是封装myBatis最终产生的sql类String sqlId = mappedStatement.getId();// 获取节点的配置Configuration configuration = mappedStatement.getConfiguration();// 获取到最终的sql语句log.info("SqlMonitorInterceptor sql after parse : {}", getSql(configuration, boundSql, sqlId));return invocation.proceed();}// 如果参数是String,则添加单引号, 如果是日期,则转换为时间格式器并加单引号;对参数是null和不是null的情况作了处理private static String getParameterValue(Object obj) {String value = null;if (obj instanceof String) {value = "'" + obj.toString() + "'";} else if (obj instanceof Date) {DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT,DateFormat.DEFAULT, Locale.CHINA);value = "'" + formatter.format(new Date()) + "'";} else {if (obj != null) {value = obj.toString();} else {value = "";}}return value;}/*** 进行 ?符号的替换* @param configuration* @param boundSql* @return*/private static String parseSql(Configuration configuration, BoundSql boundSql) {// 获取参数Object parameterObject = boundSql.getParameterObject();List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();// sql语句中多个空格都用一个空格代替String sql = boundSql.getSql().replaceAll("[\\s]+", " ");if (!CollectionUtils.isEmpty(parameterMappings) && parameterObject != null) {// 获取类型处理器注册器,类型处理器的功能是进行java类型和数据库类型的转换TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();// 如果根据parameterObject.getClass()可以找到对应的类型,则替换if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {sql = sql.replaceFirst("\\?",Matcher.quoteReplacement(getParameterValue(parameterObject)));} else {// MetaObject主要是封装了originalObject对象,提供了get和set的方法用于获取和设置originalObject的属性值,主要支持对JavaBean、Collection、Map三种类型对象的操作MetaObject metaObject = configuration.newMetaObject(parameterObject);for (ParameterMapping parameterMapping : parameterMappings) {String propertyName = parameterMapping.getProperty();if (metaObject.hasGetter(propertyName)) {Object obj = metaObject.getValue(propertyName);sql = sql.replaceFirst("\\?",Matcher.quoteReplacement(getParameterValue(obj)));} else if (boundSql.hasAdditionalParameter(propertyName)) {// 该分支是动态sqlObject obj = boundSql.getAdditionalParameter(propertyName);sql = sql.replaceFirst("\\?",Matcher.quoteReplacement(getParameterValue(obj)));} else {// 打印出缺失,提醒该参数缺失并防止错位sql = sql.replaceFirst("\\?", "缺失");}}}}return sql;}/*** 封装了一下sql语句,使得结果返回完整xml路径下的sql语句节点id + sql语句* @param configuration* @param boundSql* @param sqlId* @return*/private static String getSql(Configuration configuration, BoundSql boundSql, String sqlId){String sql = parseSql(configuration, boundSql);StringBuilder str = new StringBuilder(100);str.append(sqlId);str.append(":");str.append(sql);return str.toString();}}

自定义租户配置注解

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TenantIdInjectConfig {boolean ignore() default false;}

插件配置到SqlSession

/*** Full(proxyBeanMethods = true) :proxyBeanMethods参数设置为true时即为:Full 全模式。该模式下注入容器中的同一个组件无论被取出多少次都是同一个bean实例,即单实例对象,在该模式下SpringBoot每次启动都会判断检查容器中是否存在该组件* Lite(proxyBeanMethods = false) :proxyBeanMethods参数设置为false时即为:Lite 轻量级模式。该模式下注入容器中的同一个组件无论被取出多少次都是不同的bean实例,即多实例对象,在该模式下SpringBoot每次启动会跳过检查容器中是否存在该组件*/
@Configuration(proxyBeanMethods = true)
@ConditionalOnProperty(prefix = "users.mybatis.custom", name = "interceptor", havingValue = "true")
@Slf4j
public class MybatisConfig implements InitializingBean {@Bean@Order(1)public TenantIdInjectInterceptor mybatisInterceptor() {log.info("TenantIdInjectInterceptor interceptor init...");return new TenantIdInjectInterceptor();}/*** @Order(0) 是为了保证 TenantIdInjectInterceptor拦截器先于sqlMonitorInterceptor拦截器执行* @return*/@Bean@Order(0)public SqlMonitorInterceptor sqlMonitorInterceptor() {log.info("SqlMonitorInterceptor interceptor init...");return new SqlMonitorInterceptor();}@Overridepublic void afterPropertiesSet() throws Exception {}}

分析:

  • 这里将插件定义bean,交给容器管理即可,Mybatis会自动检测并加载到SqlSession

配置中心

数据库配置 - users-dev.properties

## mybatis依赖
spring.datasource.url = jdbc:mysql://localhost:3306/mac_m1?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=12345678
#数据库名称
spring.datasource.name=mac_m1
#默认情况下mybatis是不开启SQL日志输出,需要手动配置,com.riant.dao为mapper文件夹路径
logging.level.com.riant.mybatis.dao=debug
#指定mapper的配置文件的路径是mapper文件夹下的所有 xml文件。
mybatis.mapper-locations=classpath:mapper/*.xml

服务接口和应用配置

配置中心读取配置-bootstrap.properties

# 使用配置中心的master分支的{service-name}-dev.properties配置内容
spring.cloud.config.discovery.enabled=true
spring.cloud.config.discovery.serviceId=config-server
spring.cloud.config.profile=dev
spring.cloud.config.label=master

服务Mybatis配置-application.properties

server.port=8891## mybatis依赖
#指定mapper的配置文件的路径是mapper文件夹下的所有 xml文件。
mybatis.mapper-locations=classpath:mapper/*.xml
#默认情况下mybatis是不开启SQL日志输出,需要手动配置,com.riant.dao为mapper文件夹路径
logging.level.com.riant.mybatis.dao=debug## 自定义sql拦截器
users.mybatis.custom.interceptor=true

controller

@RestController
public class MybatisController {@Autowiredprivate UserService userService;@PostMapping("/user_insert")public UserDetail user_insert() {UserDetail detail = UserDetail.builder().age(new Random().nextInt(100)).email(new Random().nextInt(100000000) + "@qq.com").name("bryant" + new Random().nextInt(1111)).tenantId(new Random().nextLong()).build();return userService.insert(detail);}@GetMapping("/user_select")public UserDetail user_select(@RequestParam("id") Long id) {return userService.getById(id);}}

service

public interface UserService {UserDetail insert(UserDetail detail);UserDetail getById(Long id);void update(UserDetail detail);void delete(Long id);}@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Overridepublic UserDetail insert(UserDetail detail) {if (Objects.isNull(detail)) {throw new RuntimeException("detail is null");}userMapper.insert(detail);return detail;}@Overridepublic UserDetail getById(Long id) {return userMapper.getById(id);}@Overridepublic void update(UserDetail detail) {userMapper.updateById(detail);}@Overridepublic void delete(Long id) {userMapper.deleteById(id);}
}

mapper

@Repository
public interface UserMapper {void insert(@Param("record") UserDetail user);@TenantIdInjectConfig(ignore = true)void updateById(@Param("record") UserDetail user);@TenantIdInjectConfig(ignore = true)void deleteById(@Param("id") Long id);UserDetail getById(@Param("id") Long id);}

model

@Data
@Builder
public class UserDetail implements Serializable {private static final long serialVersionUID = 2235541748764244156L;private Long id;private String name;private Integer age;private String email;private Long tenantId;}

服务测试

注册服务

http://localhost:8881/,可以看到注册中心和user服务已经注册上去了。

79fd419142fc5b917a26e167ed9ee299.png

增删改查

7a414724466d4fea0a54eb57b4e4c8e4.png

1d4b231bdf9c52e9f717b9d00d40ec57.png

拦截器日志

2024-08-26 22:51:52.756 DEBUG [users,28ec46443db7b606,28ec46443db7b606,true] 3427 --- [nio-8891-exec-8] o.s.web.servlet.DispatcherServlet        : GET "/user_select?id=1", parameters={masked}
2024-08-26 22:51:52.757  INFO [users,28ec46443db7b606,28ec46443db7b606,true] 3427 --- [nio-8891-exec-8] c.b.c.mysql.TenantIdInjectInterceptor    : TenantIdInjectInterceptor interceptor start...
2024-08-26 22:51:52.758  INFO [users,28ec46443db7b606,28ec46443db7b606,true] 3427 --- [nio-8891-exec-8] c.b.config.mysql.SqlMonitorInterceptor   : SqlMonitorInterceptor interceptor start...
2024-08-26 22:51:52.758  INFO [users,28ec46443db7b606,28ec46443db7b606,true] 3427 --- [nio-8891-exec-8] c.b.config.mysql.SqlMonitorInterceptor   : SqlMonitorInterceptor original sql: select`id`, `name`, age, email, tenant_idfrom userwhere id = ?and tenant_id = ?
2024-08-26 22:51:52.758  INFO [users,28ec46443db7b606,28ec46443db7b606,true] 3427 --- [nio-8891-exec-8] c.b.config.mysql.SqlMonitorInterceptor   : SqlMonitorInterceptor sql after parse : com.bryant.mapper.UserMapper.getById:select `id`, `name`, age, email, tenant_id from user where id = 1 and tenant_id = 1
2024-08-26 22:51:52.761 DEBUG [users,28ec46443db7b606,28ec46443db7b606,true] 3427 --- [nio-8891-exec-8] o.s.web.servlet.DispatcherServlet        : Completed 200 OK

总结

Mybatis的拦截器,原理上使用了非常多的设计模式,可以参考文章Mybatis链路分析:JDK动态代理和责任链模式的应用、Mybatis插件-租户ID的注入&拦截应用:

  • 动态代理技术,Mybatis对执行器Executor使用了动态代理增强技术,从而让拦截器得以实现拦截功能;

  • 责任链模式,利用拦截器chain,Executor的各个handler进行非侵入式拦截处理。

SpringBoot集成Mybatis,跟传统的开发有比较大的异同,挖个坑,下文我们继续剖析一下源码流程。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com