Mybatis-Plus知识点详解

Mybatis-plus(简称MP),基于Mybatis的增强工具,保留了Mybatis的所有功能,同时增加了通用的CRUD,条件构造器,分页插件等等实用工具

特性

  • 即拿即用:通过通用Mapper和Service,无需编写XML既可以完成单表CURE操作
  • Lambda支持:使用Lambda表达式构建查询条件,避免硬编码字段名,提升代码安全性
  • 更多的主键策略:支持多种主键生成方式,如雪花算法,自增等等
  • 分页插件:内置分页插件,支持多种数据库,简化分页查询操作

与Mybatis相比

  • 提供大量自动化功能,如通用的CRUD,条件构造器,分页支持极大减少操作代码,提高开发效率
  • Mybatis需手动编写SQL,编写XML文件和映射接口DAO

快速入门

日志配置

  • 日志输出到标准输出流中
yaml 复制代码
mybatis-plus:
  # mapper配置文件
  mapper-locations: classpath:mapper/*.xml
  # resultType别名,没有这个配置resultType包名要写全,配置后只要写类名
  type-aliases-package: com.mashang.springboot04.domain
  //
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

添加依赖与配置application.yml

  • 本人使用SpringBoot 2.7.18,JDK1.8:导入的依赖如下
xml 复制代码
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3.2</version>
        </dependency>
  • application.yml如下:
yaml 复制代码
# 也是springboot的配置文件,两个都可以用
server:
  port: 8080
  servlet:
    #配置根路径
    context-path: /
# 配置数据源
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/xiaomi_store?userSSL=false;serverTimezone=Asia/Shanghai
    username: root
    password: root
mybatis-plus:
  # mapper配置文件
  mapper-locations: classpath:mapper/*.xml
  # resultType别名,没有这个配置resultType包名要写全,配置后只要写类名
  type-aliases-package: com.mashang.springboot03.domain

定义实体类与Mapper接口

实体类

  • 在实体类上使用Mybatis-plus的注解
  • @TableName()→指定数据库查询的表,不添加默认将类名改为下划线型数据库名
  • @TableId(name,type)→用于给主键标记,name为数据库对应名,用于指定自增算法
    • 雪花算法(Snowflake)是由Twitter开源的一种分布式ID生成算法.其核心思想是将64位的long型ID分为四个部分,分别为:符号位,时间戳,工作机器ID,序列号
java 复制代码
@Data
@TableName("user")  // 指定数据库表名
public class User {
    @TableId(type = IdType.AUTO)  // 主键自增
    private Long id;
    private String name;
    private Integer age;
}

Mapper接口

  • Mapper接口需继承BaseMapper<T>类,则此接口即可获得所有基本的CRUD方法
java 复制代码
public interface UserMapper extends BaseMapper<User> {
    // 如果需要扩展自定义方法,也可以在此添加
}

Service层与Service实现层

MP也提供了IService接口和ServiceImpl类方便在Service层使用封装好的CRUD方法

Service层

  • extends(继承)IService
    接口
java 复制代码
public interface UserService extends IService<User> {
    // 可以在此定义更多业务方法
}

ServiceImpl层

  • **继承 ServiceImpl<UserMapper, User> 和实现 IService<User> 接口,**可以直接调用封装好的 CRUD 方法
java 复制代码
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    // 继承了 ServiceImpl 后,已拥有 BaseMapper 中所有的 CRUD 方法
}

BaseMapper 接口中常用的CRUD方法

  • 即Mapper层中的常见方法,已经集成好了,直接操作数据库的原子性方法

插入操作

java 复制代码
// 插入一条记录
int insert(T entity);

查询操作

java 复制代码
// 根据 ID 查询
T selectById(Serializable id);
// 根据 entity 条件,查询一条记录
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

// 查询(根据ID 批量查询)
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 entity 条件,查询全部记录
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 查询(根据 columnMap 条件)
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
// 根据 Wrapper 条件,查询全部记录
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

// 根据 entity 条件,查询全部记录(并翻页)
IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录(并翻页)
IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询总记录数
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

删除操作

java 复制代码
// 根据 entity 条件,删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
// 删除(根据ID 批量删除)
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 ID 删除
int deleteById(Serializable id);
// 根据 columnMap 条件,删除记录
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

更新操作

java 复制代码
// 根据 whereWrapper 条件,更新记录
int update(@Param(Constants.ENTITY) T updateEntity, @Param(Constants.WRAPPER) Wrapper<T> whereWrapper);
// 根据 ID 修改
int updateById(@Param(Constants.ENTITY) T entity);

IService 接口中的常用方法

  • 即service层常见方法,业务逻辑层,可组合多个 Mapper 方法,添加事务等

插入操作(save)

java 复制代码
// 插入一条记录(选择字段,策略插入)
boolean save(T entity);
// 插入(批量)
boolean saveBatch(Collection<T> entityList);
// 插入(批量)
boolean saveBatch(Collection<T> entityList, int batchSize);

存在更新,不存在插入(saveOrUpdate)

  • boolean saveOrUpdate(T entity);:根据@TableId标识的主键判断,若主键不存在→执行INSERT,若主键存在→执行UPDATE
  • boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper);:先根据updateWrapper尝试进行更新操作,若影响行数为0,则回退到saveOrUpdate(T entity)逻辑(根据主键判断是否更新或插入)
  • saveOrUpdateBatch(Collection<T>entityList)saveOrUpdateBatch(Collection<T> entityList, int batchSize): 批量处理集合中的实体,逻与 saveOrUpdate(T) 一致

删除操作(Remove)

可配合 @TableLogic实现逻辑删除

java 复制代码
// 根据 Wrapper 构造的条件删除记录 支持复杂条件
boolean remove(Wrapper<T> queryWrapper);
// 根据 ID 删除
boolean removeById(Serializable id);
//根据 columnMap 中的字段值匹配删除记录 仅支持等值条件,
boolean removeByMap(Map<String, Object> columnMap);
// 删除(根据ID 批量删除)
boolean removeByIds(Collection<? extends Serializable> idList);

更新操作(Update)

必须设置 SET 字段, 如果未调用 .set(),SQL会缺少更新内容导致错误

java 复制代码
// 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
boolean update(Wrapper<T> updateWrapper);
// 根据 whereWrapper 条件,更新记录
boolean update(T updateEntity, Wrapper<T> whereWrapper);
// 根据 ID 选择修改
boolean updateById(T entity);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList, int batchSize);

查询单个对象(getOne)

若实体类配置了 @TableLogic,查询时会自动过滤已删除数deleted = 0

java 复制代码
// 根据 ID 查询
T getById(Serializable id);

// 根据 Wrapper,查询一条记录,结果集,如果是多个会抛出异常, 随机取一条加上限制条件 wrapper.last("LIMIT 1")
T getOne(Wrapper<T> queryWrapper);

// 根据 Wrapper,查询一条记录,可控制是否在结果不唯一时抛出异常
//不抛出异常,返回第一条记录
T getOne(Wrapper<T> queryWrapper, boolean throwEx);

// 根据 Wrapper,查询一条记录
Map<String, Object> getMap(Wrapper<T> queryWrapper);

// 根据 Wrapper,查询一条记录
<V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);

查询列表(list)

java 复制代码
// 查询所有
List<T> list();

// 查询列表
List<T> list(Wrapper<T> queryWrapper);

// 查询(根据ID 批量查询)
Collection<T> listByIds(Collection<? extends Serializable> idList);

// 查询(根据 columnMap 条件)
Collection<T> listByMap(Map<String, Object> columnMap);

// 查询所有列表
List<Map<String, Object>> listMaps();

// 查询列表
List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);

// 查询全部记录
List<Object> listObjs();

// 查询全部记录
<V> List<V> listObjs(Function<? super Object, V> mapper);

// 根据 Wrapper 条件,查询全部记录
List<Object> listObjs(Wrapper<T> queryWrapper);

// 根据 Wrapper 条件,查询全部记录
<V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);

分页查询(page)

  • 分页对象Page

    • 主要属性:
      • current:当前页码,从 1 开始
      • size:每页显示的记录数
      • searchCount:是否执行 SELECT COUNT(*) 查询总记录数,不统计总数适用于大数据量场景
    java 复制代码
    Page<User> pageParam = new Page<>(1, 10);
    LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
    wrapper.gt(User::getAge, 25)
           .orderByDesc(User::getCreateTime);
    
    IPage<User> userPage = userService.page(pageParam, wrapper);
java 复制代码
// 无条件分页查询
IPage<T> page(IPage<T> page);
// 条件分页查询
IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);
// 无条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page);
// 条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page, Wrapper<T> queryWrapper);

查询记录数(count)

java 复制代码
// 查询总记录数
int count();
// 根据 Wrapper 条件,查询总记录数
int count(Wrapper<T> queryWrapper);
java 复制代码
// 统计年龄大于25且状态为激活的用户数量
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getAge, 25)
       .eq(User::getStatus, 1);

int count = userService.count(wrapper);

条件构造器

  • 条件构造器用于动态生成SQL中的WHERE条件 ,替代手动拼接SQL语句,MP中提供了两种主要的条件构造器:QueryWrapper与LambdaQueryWrapper
    • QueryWrapper:基于字符串名的条件构造器,写法直观,但存在字段拼写错误的风险
    • LambdaQuertWrapped:基于Lambda表达式的构造器,避免了硬编码字段,更加推荐使用

常用方法

等于.eq()

  • 用于生成字段=值的条件

    • 使用字符串
    java 复制代码
    new QueryWrapper<User>().eq("age", 25); // 生成 SQL: WHERE age = 25
    • 使用Lambda表达式
    java 复制代码
    new LambdaQueryWrapper<User>().eq(User::getAge, 25);

不等于.nq()

  • 生成字段<>值条件
java 复制代码
new LambdaQueryWrapper<User>().ne(User::getStatus, 0); // SQL: WHERE status <> 0

模糊查询.like()

  • 生成字段LIKE '%值%'条件

    java 复制代码
     new LambdaQueryWrapper<User>().like(User::getName, "张"); // SQL: WHERE name LIKE '%张%'

逻辑连接or()与and()

  • or():一般用法
java 复制代码
new LambdaQueryWrapper<User>()
    .eq(User::getAge, 25)
    .or()
    .eq(User::getName, "张三");
// 生成 SQL: WHERE age = 25 OR name = '张三'
  • 嵌套用法
java 复制代码
new LambdaQueryWrapper<User>()
    .or(wrapper -> wrapper
        .eq(User::getAge, 25)
        .ne(User::getStatus, 0)
    );
// 生成 SQL: OR (age = 25 AND status <> 0)
  • and():作用是拼接多个条件,用 AND 连接默认条件连接就是 AND,一般无需显式调用

动态查询

  • 在查询条件前加上布尔判断,true则执行
java 复制代码
new LambdaQueryWrapper<User>()
    .eq(age != null, User::getAge, age) // 当 age 不为 null 时生效
    .like(StringUtils.isNotBlank(name), User::getName, name); // 当 name 非空时生效

分页查询

  • MP中内置了分页插件,使用Page对象来封装分页参数和结果

  • 在Springboot中通过配置类启动分页插件

    • 启用 MyBatis-Plus 的分页功能,使得在查询时可以直接使用分页方法
    java 复制代码
    @Configuration
    public class MyBatisPlusConfig {
        // MybatisPlus的配置
        @Bean
        public MybatisPlusInterceptor mybatisPlusInterceptor() {
            MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
            // 在拦截器中加入了一个分页的拦截器
            // 指定数据库类型为 MySQL
            interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
            return interceptor;
        }
    }
    java 复制代码
    LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
    wrapper.gt(User::getAge, 20);
    Page<User> pageParam = new Page<>(1, 10);
    IPage<User> userPage = userService.page(pageParam, wrapper);

进阶操作

敏感字段屏蔽

  • 某些字段直接发给前端会被用户恶意使用,导致安全隐患.有些字段(如日志字段,update_time,内部状态等等)对前端并无必要,过滤这些可以减少网络数据量,提高性能

方案一:手动赋值

  • 手动创建VO对象,并逐字赋值
java 复制代码
List<StudentVo> studentVos = new ArrayList<>();
for (Student s : students) {
    StudentVo vo = new StudentVo();
    vo.setName(s.getName());
    vo.setClassId(s.getClassId());
    // 其它字段也需手动赋值
    studentVos.add(vo);
}
  • 优点:可控,不存在反射性能开销
  • 缺点:当字段过多时,代码冗长,维护成本高,若实体类改变,需要改变多个地方

方案二:BeanUtils.copyProperties

  • 实现方法:利用反射机制自动复制同名属性
java 复制代码
List<StudentVo> studentVos = new ArrayList<>();
for (Student s : students) {
    StudentVo vo = new StudentVo();
		BeanUtils.copyProperties(student, vo);
    studentVos.add(vo);
}
  • 优点:代码简洁,快速复制
  • 缺点:反射操作性能慢,约为手动复制的10倍,缺乏灵活性

方案三:MapStruct 推荐

  • 导入MapStruct依赖

    xml 复制代码
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>1.4.2.Final</version>
    </dependency>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct-processor</artifactId>
        <version>1.4.2.Final</version>
    </dependency>
  • 建一个专门用于映射的文件夹mapping,编写映射接口,利用MapStruct在编译阶段自动生成高效映射代码

java 复制代码
@Mapper//注意mapper注解不要引错了,要引入mapstruct包下的
public interface StudentMapping {
    StudentMapping INSTANCE = Mappers.getMapper(StudentMapping.class);
    StudentVo toStudentVo(Student student);
    List<StudentVo> toStudentVoList(List<Student> list);
}
java 复制代码
//使用方法如下:
List<StudentVo> vos = StudentMapping.INSTANCE.toStudentVoList(students);
  • 优点:性能高,生成的代码类似于手动赋值,运行时没有反射开销,灵活性高

联表分页查询

  • 假设需要查询主表数据(如商品),及其关联的子表数据(如评论),支持分页和条件过滤.以商品(主表 )和评论(子表)为例

集中式

  • 核心逻辑是提供JOIN查询主表和子数据,在单次SQL中完成所有数据的获取

  • 代码实例

    • mapper.xml
    xml 复制代码
    <!-- 商品和评论联表查询 -->
    <select id="selectProductWithComments" resultMap="productWithCommentsMap">
        SELECT 
            p.id, 
            p.name, 
            p.price,
            c.id AS comment_id,
            c.content,
            c.user_id
        FROM product p
        LEFT JOIN comment c ON p.id = c.product_id
        ${ew.customSqlSegment}  <!-- 动态条件 -->
    </select>
    
    <!-- 结果映射 -->
    <resultMap id="productWithCommentsMap" type="ProductVO">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="price" column="price"/>
        
        <collection property="comments" ofType="Comment">
            <id property="id" column="comment_id"/>
            <result property="content" column="content"/>
            <result property="userId" column="user_id"/>
        </collection>
    </resultMap>
    • 核心点:
      • 使用 处理一对多的关系,将子表数据映射为集合

      • ${ew.customSqlSegment}

        • 其是MP中用于动态拼接的特殊占位符 ,将QueryWrapperLambdaQueryWrapper中定义的查询条件如(eq,like,between),转换为合法的Sql片段,并插入到XML映射文件的SQL语句中
        • mapper接口定义
        java 复制代码
        // 方法参数中必须声明 @Param(Constants.WRAPPER)
        Page<ProductVO> selectPage(
            @Param("page") Page<ProductVO> page, 
            @Param(Constants.WRAPPER) Wrapper<ProductVO> wrapper // 关键参数
        );
        • XML的使用
        xml 复制代码
        <select id="selectPage" resultMap="productMap">
            SELECT * FROM product
            ${ew.customSqlSegment}  <!-- 动态插入 WHERE 条件 -->
        </select>
        • 核心机制
          • ew是Wrapper的别名,对应Mapper接口方法中的@Param(Constans.WRAPPER)参数
          • 当调用wrapper.eq("name","张三")时,会自动赋值为WHERE name= '张三'如WHERE name = '手机'需直接拼接,不能作为预编译参数

分布式(嵌套查询)

  • 分为两次查询:1.分页查询主表()商品表,2.子查询:根据主表id批量查询关联的子表数据(评论表)
  • 代码实例
xml 复制代码
<!-- 主查询 -->
<select id="selectProductPage" resultMap="productPageMap">
    SELECT id, name, price FROM product
    ${ew.customSqlSegment}  <!-- 动态条件 -->
</select>

<!-- 嵌套子查询 根据商品ID查评论 -->
<select id="selectCommentsByProductIds" resultType="Comment">
    SELECT id, content, user_id, product_id 
    FROM comment 
    WHERE product_id IN 
    <foreach collection="productIds" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>

<!-- 结果映射 -->
<resultMap id="productPageMap" type="ProductVO">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <result property="price" column="price"/>
    <collection 
        property="comments" 
        ofType="Comment"
        select="selectCommentsByProductIds"  <!-- 关联子查询方法 -->
        column="{productIds=id}"             <!-- 传递参数 -->
    />
</resultMap>

字段填充

  • 再更新或者新增行时,通常需要添加一些属性如创建时间,更新时间,操作人员,MP提供了一种方法自动化处理填充

代码层面的实现

  1. 再实体类中标记需要填充的字段

    1. 使用@TableFiled主键的fill属性指定填充策略
    java 复制代码
    @Data
    public class Orders implements Serializable {
        private static final long serialVersionUID = -29854105041572661L;
    
        @TableId(type = IdType.ASSIGN_ID)
        private Long orderId;
    
        private String orderNum;
    
        private Integer userId;
    
        @TableField(fill = FieldFill.INSERT)
        private Date orderTime;
        /**
         * 创建者
         */
        @TableField(fill = FieldFill.INSERT)
        private String createBy;
        /**
         * 创建时间
         */
        @TableField(fill = FieldFill.INSERT)
        private Date createTime;
        /**
         * 更新者
         */
        @TableField(fill = FieldFill.INSERT_UPDATE)
        private String updateBy;
        /**
         * 更新时间
         */
        @TableField(fill = FieldFill.INSERT_UPDATE)
        private Date updateTime;
        /**
         * 备注
         */
        private String remark;
    
    }

    b. 实现MetaObjectHandler接口

    java 复制代码
    @Slf4j
    @Component
    public class FieldHandler implements MetaObjectHandler {
        // 在插入数据时,自动填充createTime和createBy字段
        @Override
        public void insertFill(MetaObject metaObject) {
    
            log.info("start insert fill...");
            // 设置createTime字段的值为当前时间
            this.setFieldValByName("createTime", new Date(), metaObject);
    
            // 设置createBy字段的值为admin
            this.setFieldValByName("createBy", "admin", metaObject);
    
            // 设置orderTime字段的值为当前时间
            this.setFieldValByName("orderTime", new Date(), metaObject);
    
            // 设置updateTime字段的值为当前时间
            this.setFieldValByName("updateTime", new Date(), metaObject);
    
            // 设置updateBy字段的值为admin
            this.setFieldValByName("updateBy", "admin", metaObject);
        }
    
        // 在更新数据时,自动填充updateTime和updateBy字段
        @Override
        public void updateFill(MetaObject metaObject) {
    
            log.info("start update fill...");
            // 设置updateTime字段的值为当前时间
            this.setFieldValByName("updateTime", new Date(), metaObject);
    
            // 设置updateBy字段的值为admin
            this.setFieldValByName("updateBy", "admin", metaObject);
        }
    }

逻辑删除

  • 数据"删除"后仍保留在数据库中,通过标记is_deleted字段过滤,在实体类中使用 @TableLogic 注解
  • 避免误删导致数据永久丢失,防止删除主表数据后,关联的子表数据失效(如用户删除后,历史订单需保留)
java 复制代码
public class User {
    @TableLogic
    private Integer deleted; // 1 表示删除,0 表示未删除
}
  • 当执行删除操作时,实际是在执行update操作
java 复制代码
userMapper.deleteById(1); 
//实际执行
 UPDATE user SET is_deleted = 1 WHERE id = 1;
  • 查询操作通过is_deteted过滤
java 复制代码
List<User> list = userMapper.selectList(null);
//实际执行
SELECT * FROM user WHERE is_deleted = 0;

乐观锁

  • **通过版本号(如Vesion字段)或时间戳,再数据提交时检查是否发生并非冲突,若冲突则拒绝操作,**提示重试或回滚,先操作,提交时再检查冲突,使用于低并发

MP实现乐观锁

  • 在实体类中添加版本号,并使用@Version注解标记
java 复制代码
public class Product {
    private Long id;
    private String name;
    
    @Version  // 版本号字段(MP自动管理)
    private Integer version;
}
  • 在配置类中添加乐观锁插件
java 复制代码
@EnableTransactionManagement
public class MPConfig {

    // 创建MybatisPlusInterceptor对象
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {

        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

        // 添加分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        // 添加乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}

当存在多线程的修改操作时,MP会通过SQL语句检查version版本是否一致 ,如查询商品时version为1,提交完修改时MP执行UPDATE product SET name='新名字', version=2 WHERE id=1 AND version=1;,若存在另一个线程抢先修改,则version变为2,你的更新就会失效

相关推荐
ihav2carryon19 天前
MyBatis与其使用方法讲解
#java开发#java·#mybatis
ihav2carryon1 个月前
动态代理到AOP
java开发·#后端开发
ihav2carryon1 个月前
Java反射机制
java开发·#后端开发
ihav2carryon2 个月前
Spring常用注解
java开发·#后端开发·#ssm
ihav2carryon2 个月前
Spring,Spring Ioc,Bean详解
java开发·#后端开发·#ssm·#maven
ihav2carryon5 个月前
Mysql中常用函数 分组,连接查询
java·mysql·#java开发#java·#后端开发·select语句
ihav2carryon7 个月前
JDBC,SQL注入,事务,C3P0于Druid连接池(最详细解析)
#java开发#java开发#javaweb#tomca#sevlet#底层原理·#jdbc·#后端开发·#sql注入·#连接池·#druid
ihav2carryon8 个月前
Java 文件 I/O流详解
#java开发#java