-
1、git删除分支实现步骤【转载】cid:link_62、git branch如何delete方式【转载】cid:link_73、 input的accept属性让文件上传安全高效【转载】cid:link_04、HTML5的<input>标签的`type`属性值详解和代码示例【转载】cid:link_15、 python爬虫脚本HTTP 403 Forbidden错误怎么办?【转载】cid:link_26、Python实现将.py代码转换为带语法高亮的Word和PDF【转载】cid:link_87、Python多进程中避免死锁问题的六种策略【转载】cid:link_98、 Python实现将PDF转DOCX的超简单教程(附源码)【转载】cid:link_39、Python基于OpenPyxl实现Excel转PDF并精准控制分页【转载】cid:link_1010、Python获取Docker容器实时资源占用(CPU、内存、IO等)5种实现方式【转载】cid:link_1111、 Python flash URL访问参数配置【转载】cid:link_1212、 Python利用PyMobileDevice3控制iOS设备的完整教程【转载】cid:link_413、 Python基本语法总结大全(含java、js对比)【转载】cid:link_514、 Python自动化提取多个Word文档的文本【转载】cid:link_1315、mybatis-plus分表实现案例(附示例代码)【转载】https://bbs.huaweicloud.com/forum/thread-02126200655519113076-1-1.html
-
数据库水平分表思路1. 为什么要水平分表•单表数据量过大(千万/亿级):索引膨胀、查询慢、写入压力大•单机存储瓶颈:一张表的数据撑爆单库存储•高并发读写:热点表(如订单表、日志表)写入成为瓶颈2. 核心设计要点① 确定分片键(Sharding Key)•分表路由的依据•要保证每次数据操作都能带上分片键•常见分片键:user_id、zone_code、tenant_id、时间字段② 选择分片规则•范围分表按时间区间(如 order_202501、order_202502) ✅ 适合日志、订单等时间序列数据 ❌ 跨区间查询不方便•哈希分表对分片键取模(如 user_id % 8 → 8 张表) ✅ 分布均匀,避免热点 ❌ 不方便做范围查询•混合分表先按业务维度(库级别),再在库内取模或按时间分表3.基于数据库水平分表注意事项1.目前基于mybatis-plus分表方案,只能实现单次查询单表操作,如果涉及到单次查询跨表操作就必须引入三方组件来实现(ps:ShardingSphere )2.分页或者列表查询基于mybatis-plus组件不支持组件纬度跨片查询,此时只能从业务或者分片规则上做调整,比如以年份作为分片条件,列表查询强制要求用户选择时间再进行查询3.库表必须预先创建好,预先规划好数据量4.在触发分表操作前必须能拿到分片条件示例代码测试表1234567891011121314151617181920212223242526272829303132333435-- ------------------------------ Table structure for user_1-- ----------------------------DROP TABLE IF EXISTS `user_1`;CREATE TABLE `user_1` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `age` int(2) NULL DEFAULT NULL, `email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ------------------------------ Table structure for user_2-- ----------------------------DROP TABLE IF EXISTS `user_2`;CREATE TABLE `user_2` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `age` int(2) NULL DEFAULT NULL, `email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ------------------------------ Table structure for user_3-- ----------------------------DROP TABLE IF EXISTS `user_3`;CREATE TABLE `user_3` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `age` int(2) NULL DEFAULT NULL, `email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;pom依赖12345<dependency> <artifactId>gpmscloud-framework-mybatis-common</artifactId> <groupId>com.bosssoft.gpmscloud</groupId> <version>${gpmscloud.version}</version></dependency>properties配置12345678910spring.datasource.driver-class-name=com.mysql.jdbc.Driverspring.datasource.url=jdbc:mysql://192.168.xxx.xxx:xxxxx/gpx_basic?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&serverTimezone=GMT%2B8&useSSL=false&autoReconnect=truespring.datasource.username=xxxspring.datasource.password=xxx spring.datasource.type=com.alibaba.druid.pool.DruidDataSourcespring.datasource.druid.driver-class-name=com.mysql.jdbc.Driverspring.datasource.druid.url=jdbc:mysql://192.168.xxx.xxx:xxxxx/gpx_basic?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&serverTimezone=GMT%2B8&useSSL=false&autoReconnect=truespring.datasource.druid.username=xxxspring.datasource.druid.password=xxx*mybatisplus分表实现类12345678910111213141516171819@Configurationpublic class MybatisPlusBasicPlatformConfig { @Bean public MybatisPlusInterceptor multipleTablesBasicPlatformPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor(); dynamicTableNameInnerInterceptor.setTableNameHandler((sql, tableName) -> { if (ThreadContextHandler.getThreadLocal().containsKey("tableType")) { String tableType = (String) ThreadContextHandler.getThreadLocal().get("tableType"); return tableName + "_" + tableType; } else { throw new RuntimeException("未设置分表配置"); } }); interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor); return interceptor; }}验证代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687@Data@Accessors(chain = true)@TableName(value = "user")public class User { private Long id; private String name; private Integer age; private String email;} public interface UserMapper extends BaseMapper<User> { User test(); User test2();} <?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.bosssoft.gpmscloud.framework.test.mapper.UserMapper"> <resultMap type="com.bosssoft.gpmscloud.framework.test.model.po.User" id="UserMapper"> <result property="id" column="id" jdbcType="INTEGER"/> <result property="name" column="resource_id" jdbcType="VARCHAR"/> <result property="age" column="white_list" jdbcType="INTEGER"/> <result property="email" column="black_list" jdbcType="VARCHAR"/> </resultMap> <select id="test" resultMap="UserMapper" parameterType="java.lang.String"> select id, name, age, email from user </select> <select id="test2" resultMap="UserMapper" parameterType="java.lang.String"> select u1.id, u2.name, u2.age, u2.email from user u1 left join user u2 on u1.user_id = u2.user_id </select> </mapper> @RestController@Slf4j@RequestMapping("/test/multipleTables")public class MultipleTablesController { @Resource private UserMapper userMapper; /** * 验证自动生成库表 */ @ApiOperation("test") @GetMapping("/test") public Result<Boolean> testQuery() { ThreadContextHandler.getThreadLocal().put("tableType", "1"); userMapper.selectList(null); ThreadContextHandler.getThreadLocal().put("tableType", "2"); User user = new User(); user.setId(1L); user.setName("Name"); user.setAge(1); user.setEmail("EMAIL"); userMapper.insert(user); ThreadContextHandler.getThreadLocal().put("tableType", "2"); userMapper.deleteById(1L); ThreadContextHandler.getThreadLocal().put("tableType", "3"); userMapper.selectList(null); return Result.ok(true); } /** * 验证单表和多表自定义xml操作 */ @ApiOperation("testXml") @GetMapping("/testXml") public Result<Boolean> testXml() { ThreadContextHandler.getThreadLocal().put("tableType", "2"); userMapper.test(); userMapper.test2(); return Result.ok(true); }}业务分库分表实现(mybatis-plus)1.基于上下文内tableType数据进行分表读写,这里需要注意Configuration类命名最好带上服务名,MybatisPlusInterceptor类也是,这样主要是为了避免bean名称冲突2.业务可以通过上下文参数进行分表参数传递12345678910111213141516171819@Configurationpublic class MybatisPlusBasicPlatformConfig { @Bean public MybatisPlusInterceptor multipleTablesBasicPlatformPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor(); dynamicTableNameInnerInterceptor.setTableNameHandler((sql, tableName) -> { if (ThreadContextHandler.getThreadLocal().containsKey("tableType")) { String tableType = (String) ThreadContextHandler.getThreadLocal().get("tableType"); return tableName + "_" + tableType; } else { throw new RuntimeException("未设置分表配置"); } }); interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor); return interceptor; }}
-
在 Java 后端开发中,持久层框架的选择至关重要。MyBatis 以其灵活的 SQL 控制深受开发者喜爱,但其繁琐的 CRUD 操作和大量 XML 配置也带来了额外的开发成本。MyBatis-Plus(简称 MP) 是 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。它通过“无侵入、强功能、零配置”的设计理念,极大地提升了数据库操作的开发体验。一、MyBatis-Plus 简介什么是 MyBatis-Plus?定位:MyBatis 的增强工具,非替代品。特性:无侵入:只增强,不影响原有 MyBatis 功能。损耗小:启动即注入基本 CRUD,性能损耗微乎其微。配置简洁:内置通用 Mapper 和 Service,无需编写 XML。功能强大:支持 Lambda 表达式、分页、SQL 注入器、代码生成器等。支持 ActiveRecord 模式:实体类可直接操作数据库。二、快速入门:Spring Boot 集成 MP1. 添加依赖(Maven)<dependencies> <!-- Spring Boot Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- MyBatis-Plus Starter --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency> <!-- MySQL 驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- Lombok(可选,用于简化实体类) --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency></dependencies>2. 配置数据库连接(application.yml)spring: datasource: url: jdbc:mysql://localhost:3306/test_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver# MyBatis-Plus 配置mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台打印SQL # mapper-locations: classpath:mapper/*.xml # 如需XML,可配置3. 创建实体类import com.baomidou.mybatisplus.annotation.*;import lombok.Data;@Data@TableName("user") // 指定表名public class User { @TableId(type = IdType.AUTO) // 主键策略:自增 private Long id; @TableField("name") // 字段映射(可省略) private String name; private Integer age; private String email; @TableLogic // 逻辑删除字段 private Integer deleted;}4. 创建 Mapper 接口import com.baomidou.mybatisplus.core.mapper.BaseMapper;import org.apache.ibatis.annotations.Mapper;@Mapperpublic interface UserMapper extends BaseMapper<User> { // 无需实现,继承 BaseMapper 已提供 CRUD 方法}5. 启动类启用 MPimport org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication@MapperScan("com.example.demo.mapper") // 扫描 Mapper 接口public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}三、核心功能详解1. 通用 CRUD 操作MP 通过 BaseMapper<T> 提供了丰富的 CRUD 方法:@Servicepublic class UserService { @Autowired private UserMapper userMapper; public void testCRUD() { // 插入 User user = new User(); user.setName("Alice"); user.setAge(25); user.setEmail("alice@example.com"); userMapper.insert(user); // 自动填充主键 // 查询 User result = userMapper.selectById(1L); List<User> list = userMapper.selectList(null); // 查询所有 // 条件查询 QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.eq("age", 25).like("name", "A"); List<User> users = userMapper.selectList(wrapper); // 更新 User updateUser = new User(); updateUser.setId(1L); updateUser.setName("Bob"); userMapper.updateById(updateUser); // 删除 userMapper.deleteById(1L); }}2. 条件构造器(Wrapper)MP 提供了强大的 QueryWrapper、UpdateWrapper 等条件构造器,支持链式编程和 Lambda 表达式。使用普通 WrapperQueryWrapper<User> wrapper = new QueryWrapper<>();wrapper .eq("age", 25) .gt("age", 18) .like("name", "A") .orderByDesc("id");List<User> users = userMapper.selectList(wrapper);使用 Lambda Wrapper(推荐,类型安全)LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>();lambdaWrapper .eq(User::getAge, 25) .like(User::getName, "A") .orderByDesc(User::getId);List<User> users = userMapper.selectList(lambdaWrapper);3. 分页查询MP 内置分页插件,使用简单。配置分页插件@Configuration@MapperScan("com.example.demo.mapper")public class MyBatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }}使用分页// 当前第1页,每页5条Page<User> page = new Page<>(1, 5);LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.orderByDesc(User::getId);Page<User> userPage = userMapper.selectPage(page, wrapper);// 获取分页数据List<User> records = userPage.getRecords();long total = userPage.getTotal();int pages = userPage.getPages();4. 自动填充(如创建时间、更新时间)步骤1:实体类添加注解@TableField(fill = FieldFill.INSERT)private LocalDateTime createTime;@TableField(fill = FieldFill.INSERT_UPDATE)private LocalDateTime updateTime;步骤2:实现元对象处理器@Componentpublic class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); } @Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); }}5. 逻辑删除避免物理删除数据,改为标记删除状态。配置(application.yml)mybatis-plus: global-config: db-config: logic-delete-value: 1 # 已删除值 logic-not-delete-value: 0 # 未删除值实体类字段@TableLogicprivate Integer deleted;之后的 select、delete 操作会自动过滤 deleted = 1 的数据。四、代码生成器(AutoGenerator)MP 提供了代码生成器,可一键生成 Entity、Mapper、Service、Controller 等全套代码。// 3.0+ 版本使用新方式FastAutoGenerator.create("jdbc:mysql://localhost:3306/test_db", "root", "root") .globalConfig(builder -> { builder.author("YourName") // 设置作者 .outputDir(System.getProperty("user.dir") + "/src/main/java"); // 输出路径 }) .packageConfig(builder -> { builder.parent("com.example.demo") // 设置包名 .entity("entity") .mapper("mapper") .service("service") .controller("controller"); }) .strategyConfig(builder -> { builder.entityBuilder().enableLombok(); // 启用 Lombok builder.controllerBuilder().enableRestStyle(); // REST 风格 builder.addInclude("user"); // 设置需要生成的表名 }) .execute(); MyBatis-Plus 极大地简化了 MyBatis 的开发流程,其核心优势在于:减少 CRUD 代码量:90% 的基础操作无需手写。提升开发效率:条件构造器、分页、自动填充等功能开箱即用。易于集成:与 Spring Boot 无缝整合。
-
MyBatis-Plus(简称 MP)是 MyBatis 的增强工具包,提供了很多便利的功能,其中 自动赋值实体字段 是开发中非常常见的需求。本文将深入探讨 MyBatis-Plus 的自动赋值机制,并通过详细的 Java 代码和表格进行对比和解释,帮助大家更好地掌握这个功能。1. MyBatis-Plus 自动赋值概述MyBatis-Plus 提供了一种便捷的方式来自动填充实体对象中的字段,尤其是在处理一些常见的数据库操作时(如创建时间、更新时间等)。在进行数据插入、更新操作时,常常需要对某些字段进行自动填充,减少手动赋值的工作量,提升开发效率。1.1 适用场景时间戳自动填充:如 createTime, updateTime 等字段通常需要在插入或更新时自动填充为当前时间。用户信息填充:如 createBy, updateBy 等字段通常需要在操作时自动填充当前操作用户。逻辑删除标识:如 isDeleted 字段,插入时自动填充 0,更新时填充 1 表示删除。1.2 自动填充的原理MyBatis-Plus 的自动赋值字段主要通过 @TableField 注解与填充策略来实现。它通过配置特定的填充规则,使得在执行插入、更新操作时,某些字段能够自动填充。1.3 填充策略MyBatis-Plus 提供了四种常见的填充策略:FieldFill.INSERT:在插入时填充FieldFill.UPDATE:在更新时填充FieldFill.INSERT_UPDATE:在插入和更新时都填充FieldFill.DEFAULT:不指定填充策略,使用默认填充规则2. 关键注解与配置MyBatis-Plus 提供了两个关键注解用于自动赋值字段的配置:@TableField 和 @TableId。2.1@TableField注解@TableField 注解用于标识实体类中的字段,配合 fill 属性来定义自动填充的行为。通过 fill 属性,我们可以为字段指定填充策略。示例:1234@TableField(fill = FieldFill.INSERT)private Date createTime;@TableField(fill = FieldFill.INSERT_UPDATE)private Date updateTime;createTime 字段在插入时自动填充。updateTime 字段在插入和更新时自动填充。2.2@TableId注解@TableId 注解用于指定实体类中的主键字段。在某些情况下,我们也可能需要对主键字段进行自动赋值。通常来说,MyBatis-Plus 会自动处理主键生成策略(如自增、UUID 等)。示例:12@TableId(type = IdType.AUTO)private Long id;该配置表示主键 id 使用数据库自增。2.3 配置填充策略为了实现自动赋值,我们需要配置填充策略。填充策略可以在 Mapper 层通过实现 MetaObjectHandler 接口来指定。12345678910111213@Componentpublic class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, "createTime", Date.class, new Date()); // 插入时填充 createTime this.strictInsertFill(metaObject, "createBy", String.class, "admin"); // 插入时填充 createBy } @Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date()); // 更新时填充 updateTime this.strictUpdateFill(metaObject, "updateBy", String.class, "admin"); // 更新时填充 updateBy }}MetaObjectHandler 是 MyBatis-Plus 提供的接口,用于处理自动填充的逻辑。通过实现该接口,我们可以在插入和更新时自定义字段的自动填充行为。3. 使用实例与代码解析3.1 实体类示例假设我们有一个 User 实体类,包含 id, username, createTime, updateTime 等字段,并希望在进行插入和更新时自动填充时间戳。12345678910@Datapublic class User { @TableId(type = IdType.AUTO) private Long id; private String username; @TableField(fill = FieldFill.INSERT) private Date createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime;}3.2 Mapper 示例在 Mapper 接口中,我们可以直接使用 MyBatis-Plus 提供的 updateById, insert 等方法。1234@Mapperpublic interface UserMapper extends BaseMapper<User> { // 自定义的数据库操作方法}3.3 自动赋值操作示例12345678910111213@Servicepublic class UserService { @Autowired private UserMapper userMapper; public void addUser(User user) { // 自动填充的字段会被 MyBatis-Plus 处理 userMapper.insert(user); } public void updateUser(User user) { // 自动填充的字段会被 MyBatis-Plus 处理 userMapper.updateById(user); }}在上述示例中,createTime 和 updateTime 会在插入和更新时自动填充。4. 表格对比:不同填充策略行为字段类型FieldFill.INSERTFieldFill.UPDATEFieldFill.INSERT_UPDATEFieldFill.DEFAULTcreateTime仅在插入时填充不会填充会在插入和更新时填充根据默认策略,通常是 INSERTupdateTime不会填充仅在更新时填充会在插入和更新时填充根据默认策略,通常是 INSERT通过表格,我们可以看到不同填充策略的行为区别。开发者可以根据业务需求来选择合适的填充策略。5. 总结与最佳实践5.1 自动赋值的优势减少重复代码:自动填充时间戳和用户信息等常见字段,避免手动赋值。提高代码可读性:通过注解和配置清晰表达字段填充逻辑,简化代码。保证数据一致性:自动填充保证了字段值的一致性,减少人为错误。5.2 常见问题与解决方案填充不生效:请确保已正确配置 MetaObjectHandler,并且 @TableField(fill = FieldFill.xxx) 注解已加在正确的字段上。更新时覆盖:修改配置类MyMetaObjectHandlerstrictUpdateFill(): 保留更新实体对象的原有值,只在字段为空时才会进行填充。setFieldValByName(): 会强制覆盖实体对象字段的原有值,不论字段是否为空。主键策略问题:在使用 @TableId 注解时,需要明确指定主键生成策略(如 IdType.AUTO)。5.3 最佳实践使用 MetaObjectHandler 配置全局的字段填充策略。避免在业务层手动设置常规字段(如时间戳、用户信息),通过自动填充来保证一致性。尽量避免过多的手动填充,自动赋值机制能有效减少错误和重复代码。
-
前几天做个需求的时候,有几个字段在更新的时候,可能为空。想着MyBatis-Plus有注解可以直接使用,就找寻了一下。此处记录一下。我用的MyBatis-Plus的版本是 3.5.1。版本之间对于 @TableField 中的方法定义有些区别,但大体相差不大。1、FieldStrategy.IGNORED(我找的就是它)忽略判断。无论字段值是否为 null,都会拼接到 SQL 中。适合需要显式设置 null 值的场景,可能覆盖数据库默认值。2、FieldStrategy.NOT_NULL非 NULL 判断。只有字段值不为 null 时,才会拼接到 SQL 中。不会将 null 值更新到数据库。3、FieldStrategy.NOT_EMPTY非空判断。比 NOT_NULL 更严格一些。会检查是否为空字符串 (""),会检查是否为空集合。4、FieldStrategy.DEFAULT默认策略。默认值为 NOT_NULL,可以进行全局配置。5、FieldStrategy.NEVER永不加入。无论字段值是什么,都不会拼接到 SQL 中。完全排除该字段的更新/插入。适合敏感字段或只读字段。在我使用的MyBatis-Plus版本 3.5.1 中,会用到 FieldStrategy 属性的方法有,insertStrategy()、updateStrategy()、whereStrategy()。可以进行差异化配置:@TableField(insertStrategy = FieldStrategy.NOT_NULL, updateStrategy = FieldStrategy.IGNORED)private String mobile;注意使用 FieldStrategy.IGNORED 时,数据库的字段不能设置为 NOT NULL。不然会报错。FieldStrategy 是 MyBatis-Plus 灵活性的重要体现,合理使用可以大大简化数据持久层代码,同时可以保证数据操作的精确性和安全性转载自https://www.cnblogs.com/yanshajiuzhou/p/18819743
-
1. SQL注入的基本原理SQL注入是指攻击者通过在应用程序的输入参数中插入恶意SQL代码,从而欺骗数据库服务器执行非预期的命令。典型的SQL注入攻击可能导致:数据泄露(获取敏感信息)数据篡改(修改、删除数据)权限提升(获取管理员权限)服务器控制(通过数据库执行系统命令)2. MyBatis的防注入机制2.1 预编译语句(PreparedStatement)MyBatis底层使用JDBC的PreparedStatement,这是防止SQL注入的第一道防线。工作原理:123456// MyBatis生成的SQLString sql = "SELECT * FROM users WHERE id = ?"; // JDBC预编译处理PreparedStatement pstmt = connection.prepareStatement(sql);pstmt.setInt(1, userId);参数值会被JDBC驱动进行适当的转义处理,确保它们只作为数据值而非SQL语法的一部分。2.2 参数化查询(#{}语法)MyBatis提供了两种参数占位符:#{}:安全参数绑定,自动防止SQL注入123<select id="getUser" resultType="User"> SELECT * FROM users WHERE username = #{username}</select>${}:字符串替换,存在注入风险(应谨慎使用)1234<!-- 危险示例:存在SQL注入风险 --><select id="getUser" resultType="User"> SELECT * FROM users ORDER BY ${columnName}</select>2.3 动态SQL的安全处理MyBatis提供了一套安全的动态SQL标签:1234567891011<select id="findUsers" resultType="User"> SELECT * FROM users <where> <if test="username != null"> AND username = #{username} </if> <if test="email != null"> AND email = #{email} </if> </where></select>这些标签内部会自动使用参数化查询,确保动态拼接的SQL也是安全的。3. MyBatis防注入最佳实践3.1 始终优先使用#{}语法1234567// 安全@Select("SELECT * FROM users WHERE username = #{username}")User findByUsername(@Param("username") String username); // 危险!存在注入风险@Select("SELECT * FROM users WHERE username = '${username}'")User findByUsernameInsecure(@Param("username") String username);3.2 必须使用${}时的安全措施当需要使用${}进行动态表名、列名等替换时:使用白名单校验123456789<select id="queryByField" resultType="map"> SELECT * FROM ${tableName} ORDER BY <choose> <when test="orderBy == 'name'">name</when> <when test="orderBy == 'createTime'">create_time</when> <otherwise>id</otherwise> </choose></select>进行严格的输入过滤1234567public String safeColumnName(String input) { // 只允许字母、数字和下划线 if (!input.matches("^[a-zA-Z0-9_]+$")) { throw new IllegalArgumentException("Invalid column name"); } return input;}3.3 批量操作的安全处理对于IN查询,MyBatis提供了安全的处理方式:1234567<select id="findUsersByIds" resultType="User"> SELECT * FROM users WHERE id IN <foreach collection="ids" item="id" open="(" separator="," close=")"> #{id} </foreach></select>3.4 Like查询的正确写法1234567891011<!-- 安全写法 --><select id="searchUsers" resultType="User"> SELECT * FROM users WHERE username LIKE CONCAT('%', #{keyword}, '%')</select> <!-- 或者 --><select id="searchUsers" resultType="User"> SELECT * FROM users WHERE username LIKE #{pattern}</select>3.5 使用MyBatis的SQL注入过滤器可以自定义TypeHandler或插件来拦截和过滤可疑的SQL输入:12345678910111213@Intercepts({ @Signature(type= StatementHandler.class, method="parameterize", args=Statement.class)})public class SqlInjectionInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { // 检查参数中的可疑内容 // ... return invocation.proceed(); }}4. 常见误区和陷阱误认为MyBatis完全免疫SQL注入MyBatis只有在正确使用#{}时才安全,滥用${}仍然会导致注入风险。在注解SQL中使用${}123// 危险!@Select("SELECT * FROM ${tableName} WHERE id = #{id}")User findById(@Param("tableName") String tableName, @Param("id") Long id);忽略ORDER BY子句的注入风险动态排序字段必须进行白名单校验。忽略存储过程的注入风险即使调用存储过程,如果动态拼接SQL同样存在风险。5. 增强安全性的额外措施最小权限原则数据库用户只赋予必要的最小权限。启用MyBatis的SQL日志定期审查生成的SQL语句。使用安全工具扫描SQLMap、OWASP ZAP等工具可以帮助发现潜在的注入点。定期依赖更新保持MyBatis和相关依赖库的最新版本。
-
1. 嵌套查询示例1:查询用户及其关联的订单假设我们有两个表:user和order,一个用户可以有多个订单。我们想要查询所有用户及其关联的订单信息。首先,我们需要在MyBatis的Mapper文件中定义两个查询语句,一个用于查询用户,另一个用于查询订单。然后,我们可以使用foreach标签来嵌套执行这两个查询语句。123456789<!-- 查询用户 --><select id="getUser" resultType="User"> SELECT * FROM user</select> <!-- 查询订单 --><select id="getOrdersByUserId" resultType="Order"> SELECT * FROM order WHERE user_id = #{userId}</select>接下来,在Mapper文件中定义一个新的查询语句,使用foreach标签嵌套执行上述两个查询语句。123456789101112<select id="getUserWithOrders" resultMap="UserWithOrdersResultMap"> SELECT * FROM user</select> <resultMap id="UserWithOrdersResultMap" type="User"> <id property="id" column="id"/> <result property="name" column="name"/> <collection property="orders" ofType="Order"> <id property="id" column="order_id"/> <result property="amount" column="amount"/> </collection></resultMap>在上述示例中,我们使用了UserWithOrdersResultMap来映射查询结果。User类中有一个List<Order>类型的属性orders,用于存储用户的订单信息。最后,在Java代码中调用getUserWithOrders方法即可获取用户及其关联的订单信息。1User user = sqlSession.selectOne("getUserWithOrders");2. 嵌套插入示例2:插入用户及其关联的订单假设我们有两个表:user和order,一个用户可以有多个订单。我们想要插入一个用户及其关联的订单信息。首先,我们需要在MyBatis的Mapper文件中定义两个插入语句,一个用于插入用户,另一个用于插入订单。然后,我们可以使用foreach标签来嵌套执行这两个插入语句。123456789<!-- 插入用户 --><insert id="insertUser" parameterType="User"> INSERT INTO user (name) VALUES (#{name})</insert> <!-- 插入订单 --><insert id="insertOrder" parameterType="Order"> INSERT INTO order (user_id, amount) VALUES (#{userId}, #{amount})</insert>接下来,在Mapper文件中定义一个新的插入语句,使用foreach标签嵌套执行上述两个插入语句。123456<insert id="insertUserWithOrders" parameterType="User"> INSERT INTO user (name) VALUES (#{name}) <foreach collection="orders" item="order" separator=";"> INSERT INTO order (user_id, amount) VALUES (#{id}, #{order.amount}) </foreach></insert>在上述示例中,我们使用了User类的List<Order>类型的属性orders来存储用户的订单信息。最后,在Java代码中调用insertUserWithOrders方法即可插入用户及其关联的订单信息。123456789101112User user = new User();user.setName("John"); Order order1 = new Order();order1.setAmount(100); Order order2 = new Order();order2.setAmount(200); user.setOrders(Arrays.asList(order1, order2)); sqlSession.insert("insertUserWithOrders", user);以上就是处理MyBatis中foreach双层嵌套问题的完整攻略。通过使用foreach标签,我们可以轻松地处理双层嵌套的数据结构,实现复杂的查询和插入操作。
-
在XXL-JOB中打印SQL日志,通常需要结合XXL-JOB任务执行时所使用的持久化框架(如MyBatis、JPA等)的日志配置来实现。以下是针对不同场景的详细解决方案:1. 确认XXL-JOB任务的持久化框架XXL-JOB的任务调度和执行通常涉及数据库操作(如任务存储、日志记录等)。如果任务执行过程中涉及自定义的数据库操作(例如通过MyBatis或JPA操作业务数据),则需要配置相应框架的日志输出。2. 针对MyBatis的SQL日志配置如果XXL-JOB任务中使用了MyBatis进行数据库操作,可以通过以下方式打印SQL日志:方法一:在application.properties或application.yml中配置# MyBatis日志配置(适用于Spring Boot项目) logging.level.org.mybatis=DEBUG # MyBatis核心日志 logging.level.com.yourpackage.mapper=TRACE # 你的Mapper接口包路径(输出SQL和参数)方法二:在Logback/Log4j2配置文件中细化<!-- Logback示例 (logback-spring.xml) --> <configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <!-- MyBatis日志 --> <logger name="org.mybatis" level="DEBUG"/> <logger name="com.yourpackage.mapper" level="TRACE"/> <!-- 输出SQL和参数 --> <root level="INFO"> <appender-ref ref="STDOUT"/> </root> </configuration> 效果在任务执行时,控制台或日志文件中会输出类似以下内容:DEBUG c.y.m.UserMapper.selectById - ==> Preparing: SELECT id, name FROM user WHERE id = ? DEBUG c.y.m.UserMapper.selectById - ==> Parameters: 1(Integer) TRACE c.y.m.UserMapper.selectById - <== Total: 1 3. 针对JPA/Hibernate的SQL日志配置如果XXL-JOB任务中使用了JPA/Hibernate进行数据库操作,可以通过以下方式打印SQL日志:方法一:在application.properties中配置# JPA/Hibernate SQL日志配置 spring.jpa.show-sql=true # 显示SQL语句 spring.jpa.properties.hibernate.format_sql=true # 格式化SQL语句 logging.level.org.hibernate.SQL=DEBUG # Hibernate SQL日志 logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE # 参数绑定日志方法二:在Logback/Log4j2配置文件中细化<!-- Logback示例 --> <logger name="org.hibernate.SQL" level="DEBUG"/> <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE"/> 效果在任务执行时,控制台或日志文件中会输出类似以下内容:Hibernate: select user0_.id as id1_0_, user0_.name as name2_0_ from user user0_ where user0_.id=? binding parameter [1] as [INTEGER] - [1] 4. 针对XXL-JOB自身SQL日志的配置XXL-JOB本身使用数据库进行任务调度和日志存储(如xxl_job_info、xxl_job_log等表)。如果需要查看XXL-JOB自身的SQL日志(例如调度器初始化时的SQL),可以:方法一:配置XXL-JOB的日志级别XXL-JOB的日志通常通过Logback或Log4j2输出。可以在日志配置文件中增加以下配置:<!-- Logback示例 --> <logger name="com.xxl.job.core" level="DEBUG"/> <!-- XXL-JOB核心日志 --> <logger name="com.xxl.job.admin" level="DEBUG"/> <!-- XXL-JOB Admin日志 --> 方法二:配置数据源日志如果XXL-JOB使用了连接池(如HikariCP),可以配置连接池的日志级别:logging.level.com.zaxxer.hikari=DEBUG # 连接池日志5. 完整示例(MyBatis + XXL-JOB)假设你的XXL-JOB任务中使用了MyBatis进行数据库操作,以下是完整的配置示例:application.properties# XXL-JOB配置 xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin xxl.job.executor.appname=xxl-job-executor-sample xxl.job.executor.ip= xxl.job.executor.port=9999 xxl.job.accessToken= xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler xxl.job.executor.logretentiondays=30 # MyBatis日志配置 logging.level.org.mybatis=DEBUG logging.level.com.yourpackage.mapper=TRACE logback-spring.xml<configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <!-- MyBatis日志 --> <logger name="org.mybatis" level="DEBUG"/> <logger name="com.yourpackage.mapper" level="TRACE"/> <!-- XXL-JOB日志 --> <logger name="com.xxl.job.core" level="DEBUG"/> <logger name="com.xxl.job.admin" level="DEBUG"/> <root level="INFO"> <appender-ref ref="STDOUT"/> </root> </configuration> 6. 注意事项日志级别选择:DEBUG:输出SQL语句。TRACE:输出SQL语句和参数绑定(MyBatis需要配置com.yourpackage.mapper为TRACE)。生产环境建议避免使用TRACE级别,以免日志量过大。性能影响:开启SQL日志会增加日志量,可能影响性能,建议在调试时使用。XXL-JOB日志与业务日志分离:如果需要区分XXL-JOB调度日志和业务SQL日志,可以通过不同的日志文件或Appender进行配置。7. 总结MyBatis任务:配置logging.level.org.mybatis=DEBUG和logging.level.com.yourpackage.mapper=TRACE。JPA/Hibernate任务:配置spring.jpa.show-sql=true和logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE。XXL-JOB自身日志:配置logging.level.com.xxl.job.core=DEBUG。通过以上配置,你可以在XXL-JOB任务执行时清晰地看到SQL日志,便于调试和优化任务中的数据库操作。
-
在Spring Boot中使用MyBatis时,可以通过在application.properties文件中配置日志相关的属性来打印SQL日志、参数以及执行结果等信息。以下是具体的配置项和说明:1. 配置MyBatis日志级别MyBatis的日志输出依赖于底层日志框架(如Logback、Log4j2等)。首先需要确保项目中已引入相应的日志依赖(Spring Boot默认使用Logback)。在application.properties中配置MyBatis的日志实现和日志级别:# 指定MyBatis使用的日志实现(可选,通常不需要显式配置,Spring Boot会自动适配) # mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl # 配置日志级别(推荐使用Logback或Log4j2的配置文件,但也可以通过properties临时调整) logging.level.org.mybatis=DEBUG # MyBatis核心日志 logging.level.com.yourpackage.mapper=TRACE # 你的Mapper接口包路径(TRACE级别会输出SQL和参数)2. 配置数据源日志(如HikariCP)如果使用HikariCP作为数据源,可以配置其日志级别以监控连接池行为:logging.level.com.zaxxer.hikari=DEBUG 3. 配置JDBC日志(可选)如果需要更底层的JDBC日志(如SQL参数绑定),可以配置底层驱动的日志:# 例如,使用MySQL驱动时 logging.level.com.mysql.cj.jdbc=DEBUG 4. 推荐完整配置示例# 日志配置(推荐在logback-spring.xml中详细配置,此处为properties简写) logging.level.org.mybatis=DEBUG logging.level.com.yourpackage.mapper=TRACE # 替换为你的Mapper接口包路径 logging.level.com.zaxxer.hikari=DEBUG # 连接池日志 # MyBatis配置(可选) mybatis.configuration.log-prefix=DAO. # 日志前缀(可选)5. 更推荐的方式:使用Logback/Log4j2配置文件由于application.properties对日志的配置能力有限,建议通过专门的日志配置文件(如logback-spring.xml)进行更精细的控制。例如:Logback示例 (logback-spring.xml)<configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <!-- MyBatis日志 --> <logger name="org.mybatis" level="DEBUG"/> <logger name="com.yourpackage.mapper" level="TRACE"/> <!-- 输出SQL和参数 --> <!-- 连接池日志 --> <logger name="com.zaxxer.hikari" level="DEBUG"/> <root level="INFO"> <appender-ref ref="STDOUT"/> </root> </configuration> 6. 关键日志级别说明DEBUG:输出MyBatis的核心日志(如SQL语句)。TRACE:输出更详细的日志(包括SQL参数绑定和结果映射)。INFO/WARN/ERROR:常规日志级别,通常不包含SQL细节。7. 验证配置启动应用后,检查控制台或日志文件,应能看到类似以下输出:DEBUG c.y.m.UserMapper.selectById - ==> Preparing: SELECT id, name FROM user WHERE id = ? DEBUG c.y.m.UserMapper.selectById - ==> Parameters: 1(Integer) TRACE c.y.m.UserMapper.selectById - <== Total: 1 总结核心配置:在application.properties中设置logging.level.com.yourpackage.mapper=TRACE。推荐方式:使用logback-spring.xml或log4j2-spring.xml进行更灵活的日志配置。注意事项:生产环境建议避免使用TRACE级别,以免日志量过大。通过以上配置,你可以清晰地看到MyBatis执行的SQL语句、参数绑定以及结果映射过程,便于调试和优化。
-
OptimisticLockerInnerInterceptor 的作用1. 分析OptimisticLockerInnerInterceptor 是 MyBatis-Plus 框架中的一个内部拦截器,用于实现乐观锁机制。乐观锁是一种在数据库管理系统中用于控制并发访问的机制,它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。提交数据时,如果发现数据自上次读取后已经被其他用户修改过,则本次操作会被拒绝,并抛出一个异常。2. 作用2.1 防止并发修改冲突在并发环境下,多个用户可能同时修改同一条记录。如果没有任何控制机制,最后提交的数据可能会覆盖之前的数据,导致数据丢失。使用乐观锁后,每次更新数据时都会检查记录的版本号或时间戳是否发生变化,如果发生变化,则拒绝更新,从而避免并发修改冲突。2.2 提高系统吞吐量与悲观锁相比,乐观锁不需要在数据库层面加锁,因此可以减少锁竞争,提高系统的并发处理能力。这对于高并发的应用场景尤为重要。2.3 简化开发MyBatis-Plus 通过提供 OptimisticLockerInnerInterceptor 拦截器,简化了乐观锁的实现。开发者只需在实体类中添加相应的乐观锁字段(如 version 或 updateTime),并在更新操作时指定该字段,MyBatis-Plus 就会自动处理乐观锁的逻辑。3. 结论OptimisticLockerInnerInterceptor 是 MyBatis-Plus 中用于实现乐观锁机制的内部拦截器,它能够防止并发修改冲突,提高系统吞吐量,并简化开发过程。在配置 MyBatis-Plus 时,可以通过添加该拦截器来启用乐观锁功能。
-
PaginationInnerInterceptor 的作用1. 作用概述PaginationInnerInterceptor 是 MyBatis-Plus 框架中的一个分页插件,用于实现数据库查询的分页功能。它允许开发者在不修改原有 SQL 语句的情况下,通过简单的配置即可实现分页查询,从而简化了分页功能的开发过程。2. 具体功能2.1 自动分页当使用 MyBatis-Plus 进行数据库查询时,如果启用了 PaginationInnerInterceptor 插件,MyBatis-Plus 会自动在生成的 SQL 语句中添加分页相关的条件(如 LIMIT 和 OFFSET),从而实现分页查询。开发者无需手动编写分页 SQL,只需在查询方法上添加分页参数即可。2.2 支持多种数据库PaginationInnerInterceptor 支持多种数据库的分页查询,包括 MySQL、PostgreSQL、Oracle 等。通过配置 DbType 属性,可以指定当前使用的数据库类型,从而确保分页查询的正确性。2.3 可配置性PaginationInnerInterceptor 提供了丰富的配置选项,如 setOverflow(是否允许查询总数超过最大单页限制)、setMaxLimit(设置最大单页限制数量)等。这些配置选项允许开发者根据实际需求对分页插件进行灵活配置。3. 使用示例在提供的背景知识中,MybatisPlusConfig 类配置了 PaginationInnerInterceptor 插件,并设置了数据库类型为 MySQL、允许查询总数超过最大单页限制以及不限制最大单页查询数量。配置完成后,开发者只需在 Service 层或 Mapper 层的方法上添加分页参数(如 Page 对象),即可实现分页查询功能。import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserService { @Autowired private UserMapper userMapper; public Page<User> getUserPage(int current, int size) { Page<User> page = new Page<>(current, size); return userMapper.selectPage(page, null); } } 在上述示例中,getUserPage 方法接收当前页码和每页显示数量作为参数,创建一个 Page 对象,并通过 userMapper.selectPage 方法实现分页查询。MyBatis-Plus 会自动在生成的 SQL 语句中添加分页条件,并返回分页结果。
-
📚️1. #{} 与 ${}的使用我们在之前的学习中,了解到了“#{}”,但是这里的${}是什么呢?其实这里的${}也具有参数传递的功能但是我们之前为什么不使用$符号呢?且听下面的分析过程~~~1.1integer类型数据我们可以通过id类整型参数的传递进行实验,首先得先创建一个数据库,接下来我们知己使用XML的方式进行代码的编写:在Mapper类中:1List<UserInfo> select2(Integer id); 这里先定义数据的返回类型,然后再在XML文件中实现查询SQL的方法:123<select id="select2" resultType="com.example.mybatis.Model.UserInfo"> select * from user_info where id=#{id} </select>解释:可以发现在SQL查询语句中,参数被“?”给替代了,然后下面的参数就是“2”,此时可以了解到我们输⼊的参数并没有在后⾯拼接,id的值是使⽤ ? 进⾏占位. 这种SQL 我们称之为"预编译SQL"然后我们将这里的“#{ }” 替换成“${}”,具体的情况就是如下所示的:解释:可以发现此时SQL语句,就没有“?”,然后下面的paramters参数就为空了,然后我们就知道${} 会直接进⾏字符替换, ⼀起对SQL进⾏编译,不会像#号一样使用占位符;这种我们就称之为即时SQL1.2String类型数据这里我们使用String类来进行匹配查询,具体的操作还是和上面差不多;Mapper类的代码如下:1List<UserInfo> selectAllByUsername(String username);然后再XML写SQL语句的操作:解释:此时可以看到,和上面的string类型的数据是一样的,这里会标明参数的类型为string类型,然后再进行替换占位的时候会自动添加引号;然后我们将这里的“#{ }” 替换成“${}”,具体的情况就是如下所示的:123<select id="selectAllByUsername" resultType="com.example.mybatis.Model.UserInfo"> select * from user_info where username=${username} </select>这里就是通过输入的名字进行查询,将这#号替换成了$符号,然后再测试类进行测试后打印的日志报错了解释:这里可以看到此时的$符号的操作,出现了报错,原因就是BadSql,我们在上面看到,由于直接代替的原因,查询条件中字符串没有出现“ ' ' ”符号,即引号,然后就是SQL语句语法不正确导致错误;解决办法:在XML编写SQL语句的时候进行手动添加' ';代码如下:123<select id="selectAllByUsername" resultType="com.example.mybatis.Model.UserInfo"> select * from user_info where username='${username}' </select>复制免费讲解AI专家解释:此时可以看到在字符串添加了“ ' ' ”双引号,然后参数仍然为空;综上所述:#{} 使⽤的是预编译SQL, 通过 ? 占位的⽅式, 提前对SQL进⾏编译, 然后把参数填充到SQL语句中. #{} 会根据参数类型, ⾃动拼接引号 '' .${} 使用的就是即时编译SQL,会直接进⾏字符替换, ⼀起对SQL进⾏编译. 如果参数为字符串, 需要加上引号 ''📚️2. #{} 与 ${}的区别2.1性能当客⼾发送⼀条SQL语句给服务器后, ⼤致流程如下:1. 解析语法和语义, 校验SQL语句是否正确2. 优化SQL语句, 制定执⾏计划3. 执⾏并返回结果⼀条 SQL如果⾛上述流程处理, 我们称之为 Immediate Statements(即时 SQL) 但是绝⼤多数情况下, 某⼀条 SQL 语句可能会被反复调⽤执⾏, 或者每次执⾏的时候只有个别的值不同(⽐如 select 的 where ⼦句值不同, update 的 set ⼦句值不同, insert 的 values 值不同). 如果每次都需要经过上⾯的语法解析, SQL优化、SQL编译等,则效率就明显不⾏了总结: 所以预编译SQL就在执行上述的优化操作后,遇到同样的SQL语句,就不会对SQL进行再次的优化编译了,就直接改变参数,省去了解析优化等过程, 以此来提⾼效率预编译SQL的性能比即时SQL的性能更高;2.2安全性 这里出现的安全性就是(SQL注入)SQL注⼊:是通过操作输⼊的数据来修改事先定义好的SQL语句,以达到执⾏代码对服务器进⾏攻击的⽅法。由于没有对⽤⼾输⼊进⾏充分检查,⽽SQL⼜是拼接⽽成,在⽤⼾输⼊参数时,在参数中添加⼀些SQL关键字,达到改变SQL运⾏结果的⽬的,也可以完成恶意攻击注意:这里只针对的就是${ }符号;假如我们在此符号中添加 ' or 1='1然后在#{ }运行中,可以发现此时的此时的打印日志告诉我们,这里的字符串在数据库中没有找到哎奇怪,怎么会出现这种情况,这个字符串在我们的数据库列中就没有这个字段;但是为啥就全部搜索出来了呢??因为拼接的问题和原因,所以这里出现了误判,' {' or 1='1} ' 拼接后成了以下SQL语句' ' or 1='1'解释:具体的意思就是为空或者为true,这就是为啥全部搜索出来了,这里存在SQL关键字or;所以SQL注⼊是⼀种⾮常常⻅的数据库攻击⼿段, SQL注⼊漏洞也是⽹络世界中最普遍的漏洞之⼀.如果发⽣在⽤⼾登录的场景中, 密码输⼊为 ' or 1='1 , 就可能完成登录;总之在后面的学习中能用#{} 那么就使用#{ },${ }非特殊情况尽量不要使用 📚️3.${ }使用场景3.1排序在实现排序功能的时候,由于“desc”不需要使用引号,所以这里我们就可以使用${}符号具体的代码如下所示:123<select id="selectAllById" resultType="com.example.mybatis.Model.UserInfo"> select * from user_info order by id ${sort} </select>这里即时XML的写法,可以看到此时order by id然后排序关键词,就不需要引号,那么此时我们就可以使用$符号;在测试类中的实现代码:1234@Test void selectAllById() { userInfoXMLMapper.selectAllById("desc"); }我们这里就是按照降序排序进行查询结果的排序的解释:上面的两行就是SQL语句和参数,参数为空,然后即时SQL进行拼接,SQL语句就成为了一个查询语句按照降排序的方式进行查询结果的展示;3.2模糊查询代码如下所示:123<select id="selectByUsername" resultType="com.example.mybatis.Model.UserInfo"> select * from user_info where username like '%${username}%' </select>这里也是不需要引号,然后也可以使用$符号,在测试类中代码:1234@Test void selectByUsername() { userInfoXMLMapper.selectByUsername("o"); }解释:这里的模糊查询,中间的参数是不需要自动添加引号的,并且这里的模糊查询的条件就是查找名字里包含“o”的那一段数据;但是这里由于注入等安全性,这里我们可以使用#进行另一种写法,具体的代码如下所示:123<select id="selectByUsername2" resultType="com.example.mybatis.Model.UserInfo"> select * from user_info where username like concat('%',#{username},'%') </select>解释:这里使用concat关键字,实现需要引号的拼接操作,这样就可以使用#{}来进行参数的传递,几避免了SQL注入的安全问题,还可能提高了执行的效率;
-
前置工作导包:mybatis-spring、mysql-connector-java、mybatis、spring-webmvc、spring-jdbc实体类DAO层两个文件(接口、xml文件);Service层的接口spring-dao.xml核心:sqlSessionTemplate配置<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory"/></bean>完整代码:<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> <!-- 1.使用Spring配置dataSource,相当于myBatis配置文件的<environments>。需要spring-jdbc包--> <context:property-placeholder location="classpath:db.properties"/> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <!-- 2.配置SqlSessionFactoryBean 等同于SqlSessionFactory 做读取数据源以及注册mapper.xml的工作--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="classpath:com/ylzl/mapper/*.xml"/> <!-- 配置MyBatis的全局配置文件:mybatis-config.xml --> <property name="configLocation" value="classpath:mybatis-config.xml"/> </bean> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory"/> </bean> <bean id="bookMapper" class="com.ylzl.mapper.BookMapperImpl"> <property name="sqlSession" ref="sqlSession"/> </bean> <bean id="bookServiceImpl" class="com.ylzl.service.BookServiceImpl"/></beans>DAO层新加Dao接口的实现类:私有化sqlSessionTemplatepublic class BookMapperImpl implements BookMapper { //sqlSession不用我们自己创建了,Spring来管理 private SqlSessionTemplate sqlSession; public void setSqlSession(SqlSessionTemplate sqlSession) { this.sqlSession = sqlSession; } @Override public Book getBookById(Integer bookID) { BookMapper mapper = sqlSession.getMapper(BookMapper.class); return mapper.getBookById(bookID); }}注:注册bean实现(dao接口实现类的)Service层-实现类public class BookServiceImpl implements BookService{ @Autowired private BookMapperImpl bookMapperImpl; @Override public Book getBookById(Integer bookID) { return bookMapperImpl.getBookById(bookID); }}测试ApplicationContext context=new ClassPathXmlApplicationContext("spring-dao.xml"); BookService bookServiceImpl = context.getBean("bookServiceImpl", BookService.class); Book book = bookServiceImpl.getBookById(2); System.out.println(book);优化:SqlSessionDaoSupportmybatis-spring1.2.3版以上的才有这个1.修改dao层实现类public class BookMapperImpl extends SqlSessionDaoSupport implements BookMapper { @Override public Book getBookById(Integer bookID) { SqlSession sqlSession = getSqlSession(); BookMapper mapper = sqlSession.getMapper(BookMapper.class); return mapper.getBookById(bookID); }}2.注册bean实现(dao接口实现类:BookMapperImpl)<bean id="bookMapper" class="com.ylzl.mapper.BookMapperImpl"> <!-- 注意这行代码 --> <property name="sqlSessionFactory" ref="sqlSessionFactory"/></bean>3.删除<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory"/></bean>转载自https://www.cnblogs.com/yulingzhiling/p/18100409
-
前置工作导包(mybatis-spring、mysql-connector-java、mybatis、spring-webmvc等)实体类DAO层两个文件(接口、xml文件);Service层的接口编写Spring管理mybatis的xml-spring-dao.xml核心代码(两种方式实现)第一种:xml<!-- 将会话工厂对象托管给spring --><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <!-- 配置MyBatis的全局配置文件:mybatis-config.xml --> <property name="configLocation" value="classpath:mybatis-config.xml"/> <property name="mapperLocations" value="classpath:com/ylzl/mapper/*.xml"/></bean> <!-- 注册映射器:将映射器接口托管到Spring中 --><bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"> <property name="sqlSessionFactory" ref="sqlSessionFactory" /> <property name="mapperInterface" value="com.ylzl.mapper.UserMapper" /></bean><!-- MapperFactoryBean对象 负责 SqlSession 的创建和关闭,如果使用了 Spring 事务,当事务完成时,session 将会被提交或回滚。最终任何异常都会被转换成 Spring 的 DataAccessException 异常--><!-- mybatis映射器接口(如:interface UserMapper):sql部分可以使用mybatis的xml配置,与接口在同一路径下,会被 MapperFactoryBean自动解析-->第二种:annotation方式@Beanpublic SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource()); return factoryBean.getObject();} @Beanpublic MapperFactoryBean<UserMapper> userMapper() throws Exception { MapperFactoryBean<UserMapper> factoryBean = new MapperFactoryBean<>(UserMapper.class); factoryBean.setSqlSessionFactory(sqlSessionFactory()); return factoryBean;}完整xml文件<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> <!-- 使用Spring配置dataSource 相当于MyBatis配置文件的<environments>--><!-- 需要spring-jdbc包--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/ssmbuild"/> <property name="username" value="root"/> <property name="password" value="root"/> </bean> <!-- 配置SqlSessionFactoryBean 等同于SqlSessionFactory 做读取数据源以及注册mapper.xml的工作--><!-- SqlSessionFactoryBean会调用类中的getObject()方法,返回SqlSessionFactory--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="classpath:com/ylzl/mapper/BookMapper.xml"/> </bean><!-- 获得Mapper代理对象 等同于getMapper()--> <bean id="BookMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"> <property name="sqlSessionFactory" ref="sqlSessionFactory" /> <property name="mapperInterface" value="com.ylzl.mapper.BookMapper" /> </bean> <!-- 注册employeeServiceImpl--> <bean id="bookServiceImpl" class="com.ylzl.service.impl.BookServiceImpl"/></beans>重新编写Service实现类public class BookServiceImpl implements BookService{ private BookMapper bookMapper; @Autowired //需要spring-aop包 public BookServiceImpl(BookMapper bookMapper) { this.bookMapper = bookMapper; } @Override public Book getBookById(Integer bookID) { return bookMapper.getBookById(bookID); }}测试ApplicationContext context=new ClassPathXmlApplicationContext("spring-dao.xml");BookService bookServiceImpl = context.getBean("bookServiceImpl", BookService.class);Book book = bookServiceImpl.getBookById(2);System.out.println(book);改进注册映射器方式:使用发现映射器方式MapperFactoryBean注册映射器的最大问题,就是需要一个个注册所有的映射器,而实际上mybatis-spring提供了扫描包下所有映射器接口的方法。注意:以下两种配置方法,均可替换上述MapperFactoryBean配置,而其余代码与配置不变方式一: 配置扫描器标签1.与上面配置MapperFactoryBean不同,该配置无需注入SqlSessionFactory,它会自动匹配已有的会话工厂bean2.如果配置了多个DataSource,也就是多个sqlSessionFactory时,可以使用factory-ref参数指定需要的会话工厂<mybatis:scan base-package="com.ylzl.dao" factory-ref="sqlSessionFactory" /> <!-- annotation方式-注解配置类:@MapperScan(basePackages = "com.ylzl.dao", sqlSessionFactoryRef = "sqlSessionFactory") --> <!-- 省略其他... -->方式二: MapperScannerConfigurer类1.与上面标签的功能差不多,同样是扫描基准包,自动注入会话工厂2.如果要更换注入的会话工厂,不同于常用的ref引入bean,而是使用value指定bean名,且属性是sqlSessionFactoryBeanName<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.ylyl.mapper" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /></bean>annotation方式@Beanpublic MapperScannerConfigurer mapperScannerConfigurer() { MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); mapperScannerConfigurer.setBasePackage("com.ylzl.dao"); mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory"); return mapperScannerConfigurer;}转载自https://www.cnblogs.com/yulingzhiling/p/18099455
-
0、前言在MyBatis中可能会有一些特殊的SQL需要去执行,一般就是模糊查询、批量删除、动态设置表名、添加功能获取自增的主键这几种,现在分别来进行说明。为了方便演示 ,定义了访问的接口public interface SQLMapper { /** * 根据用户名模糊查询用户信息 */ List<User> getUserByLike(@Param("username") String username); /** * 批量删除 */ int deleteMore(String ids); /** * 查询指定表中的数据 */ List<User> getUserByTableName(String tableName); /** * 添加用户 */ void insetUser(User user); }1、模糊查询模糊查询非常的有用,对于一些访问量不是很大的搜索都是直接使用模糊查询的方式来做的。SQLMapper类:public interface SQLMapper { /** * 根据用户名模糊查询用户信息 */ List<User> getUserByLike(@Param("username") String username); }对于SQLXml的编写;<!-- List<User> getUserByLike(@Param("username") String username);--> <!-- 使用#{},因为包括在单引号里,会被认为是字符串的一部分:select * from t_user where username like '%#{username}%'--> <!-- 三种方式--> <select id="getUserByLike" resultType="User"> <!-- 第一种 select * from t_user where username like '%${username}%' 第二种 select * from t_user where username like concat('%', #{username}, '%')--> <!--第三种 推荐使用--> select * from t_user where username like "%"#{username}"%" </select>需要注意的是Mybatis对JDBC进行了进一步封装,使得我们可以更加便捷的使用Java操作数据库。Mybatis获取参数值有两种方式:#{}和${}在大部分情况下,#{}和${}都能相互替代,使用两者之一即可,更加推荐使用#{},因为可以防止SQL注入问题,但是由于#{}和${}本质上的不同,部分SQL语句使用#{}和${}需要格外注意#{}和${}本质区别#{}本质上是占位符赋值,为字符串类型或日期类型的字段进行赋值时,可以自动添加单引号${}本质上是字符串拼接,为字符串类型或日期类型的字段进行赋值时,需要手动加单引号这个场景下,使用#{}和${}都能达到目的,但是用法稍有不同如果是直接使用<select id="selectLike" resultType="pojo.User"> select * from user where user_name like '%#{username}%' </select>将#{}换成${}<select id="selectLike" resultType="pojo.User"> select * from user where user_name like '%${username}%' </select>成功执行如果非要使用#{},也不是没有解决办法使用""拼接<select id="selectLike" resultType="pojo.User"> select * from user where user_name like "%"#{username}"%" </select>或者是使用concat()函数来拼接<select id="selectLike" resultType="pojo.User"> select * from user where user_name like concat('%',#{likeString},'%') </select>2、动态表名在某些场景下,我们需要来回操作各种表,但SQL语句功能一致,这时我们可以使用动态表名,即传参为表名类型,这时就要从#{}和${}中进行选择了Mapper接口List<User> selectAllFromTable(@Param("tableName") String tableName);如果是直接使用#{}的方式<select id="selectAllFromTable" resultType="pojo.User"> select * from #{tableName} </select>结果报错,原因在于#{}为占位符赋值,传参为String的话就会自动补上单引号'',而表名不允许添加单引号,所以导致出错。直接使用${}的方式<select id="selectAllFromTable" resultType="pojo.User"> select * from ${tableName} </select>结果成功了,所以在动态表名的情况下,我们只能使用${}3、获取自增的组件需要在xml中配置 useGeneratedKeys , keyProperty 两个属性的值.<!-- void insetUser(User user);--> <!-- 方法的返回值是固定的 useGeneratedKeys 设置当前标签中的sql使用了自增的主键 (id) keyProperty 将自增的主键的值 赋值给 传输到映射文件中的参数的某个属性(user.id) --> <insert id="insetUser" useGeneratedKeys="true" keyProperty="id"> insert into t_user values(null, #{username}, #{password},#{age},#{gender},#{email}) </insert>4、批量删除有些场景,需要我们根据id数组批量删除记录,这个时候也有一些坑由于id数组的长度是不确定的,所以我们不能确定参数的个数,但是我们可以使用in关键字,这个时候我们将id数组转为字符串进行传参就好了。[1,2,3] => 1,2,3Mapper接口Integer deleteByIds(String Ids);使用#{}<delete id="deleteByIds"> delete from user where id in (#{ids}) </delete>结果报错,原因在于in后面的小括号里面的'1,2,3'为字符串类型且为一个整体,与整数类型不符,因此不能使用#{}使用${}<delete id="deleteByIds"> delete from user where id in (${ids}) </delete>结果成功了,看来有些场景不得不使用${}
推荐直播
-
HDC深度解读系列 - Serverless与MCP融合创新,构建AI应用全新智能中枢2025/08/20 周三 16:30-18:00
张昆鹏 HCDG北京核心组代表
HDC2025期间,华为云展示了Serverless与MCP融合创新的解决方案,本期访谈直播,由华为云开发者专家(HCDE)兼华为云开发者社区组织HCDG北京核心组代表张鹏先生主持,华为云PaaS服务产品部 Serverless总监Ewen为大家深度解读华为云Serverless与MCP如何融合构建AI应用全新智能中枢
回顾中 -
关于RISC-V生态发展的思考2025/09/02 周二 17:00-18:00
中国科学院计算技术研究所副所长包云岗教授
中科院包云岗老师将在本次直播中,探讨处理器生态的关键要素及其联系,分享过去几年推动RISC-V生态建设实践过程中的经验与教训。
回顾中 -
一键搞定华为云万级资源,3步轻松管理企业成本2025/09/09 周二 15:00-16:00
阿言 华为云交易产品经理
本直播重点介绍如何一键续费万级资源,3步轻松管理成本,帮助提升日常管理效率!
回顾中
热门标签