Java高手的30k之路|面试宝典|精通MyBatis(二)

基本操作

CRUD

在MyBatis中,Mapper接口用于定义基本的CRUD(Create, Read, Update, Delete)操作。可以使用XML配置文件或Java注解来定义这些操作。以下是使用这两种方式定义基本CRUD操作的示例。

使用XML配置文件

1. 定义Mapper接口
public interface UserMapper {
    User selectUser(int id);
    List<User> selectAllUsers();
    void insertUser(User user);
    void updateUser(User user);
    void deleteUser(int id);
}
2. 配置Mapper XML文件
<mapper namespace="com.example.mapper.UserMapper">
    <select id="selectUser" resultType="com.example.domain.User" parameterType="int">
        SELECT * FROM users WHERE id = #{id}
    </select>

    <select id="selectAllUsers" resultType="com.example.domain.User">
        SELECT * FROM users
    </select>
    
    <insert id="insertUser" parameterType="com.example.domain.User">
        INSERT INTO users (name, email) VALUES (#{name}, #{email})
    </insert>
    
    <update id="updateUser" parameterType="com.example.domain.User">
        UPDATE users SET name = #{name}, email = #{email} WHERE id = #{id}
    </update>
    
    <delete id="deleteUser" parameterType="int">
        DELETE FROM users WHERE id = #{id}
    </delete>
</mapper>

使用注解

1. 定义Mapper接口并使用注解
import org.apache.ibatis.annotations.*;

public interface UserMapper {
    @Select("SELECT * FROM users WHERE id = #{id}")
    User selectUser(int id);

    @Select("SELECT * FROM users")
    List<User> selectAllUsers();

    @Insert("INSERT INTO users (name, email) VALUES (#{name}, #{email})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    void insertUser(User user);

    @Update("UPDATE users SET name = #{name}, email = #{email} WHERE id = #{id}")
    void updateUser(User user);

    @Delete("DELETE FROM users WHERE id = #{id}")
    void deleteUser(int id);
}

示例代码说明

  • Select操作:用于查询单条记录或多条记录,使用@Select注解或<select>标签。
  • Insert操作:用于插入记录,使用@Insert注解或<insert>标签。通常会使用@Options注解来获取生成的主键。
  • Update操作:用于更新记录,使用@Update注解或<update>标签。
  • Delete操作:用于删除记录,使用@Delete注解或<delete>标签。

使用Mapper接口

public class MyBatisExample {
    public static void main(String[] args) {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        try (SqlSession session = sqlSessionFactory.openSession()) {
            UserMapper mapper = session.getMapper(UserMapper.class);

            // 插入用户
            User newUser = new User();
            newUser.setName("John");
            newUser.setEmail("john@example.com");
            mapper.insertUser(newUser);
            session.commit();

            // 查询用户
            User user = mapper.selectUser(newUser.getId());
            System.out.println(user);

            // 更新用户
            user.setName("Jane");
            user.setEmail("jane@example.com");
            mapper.updateUser(user);
            session.commit();

            // 删除用户
            mapper.deleteUser(user.getId());
            session.commit();

            // 查询所有用户
            List<User> users = mapper.selectAllUsers();
            users.forEach(System.out::println);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

动态SQL

MyBatis 提供了一组动态 SQL 标签,这些标签可以帮助生成动态 SQL 语句,使得 SQL 语句更灵活和可重用。以下是 MyBatis 动态 SQL 标签及其用法的详细解释:

1. <if> 标签

<if> 标签用于根据条件动态地包含 SQL 片段。

示例
<select id="selectUser" parameterType="map" resultType="User">
    SELECT * FROM users
    <where>
        <if test="id != null">
            AND id = #{id}
        </if>
        <if test="name != null">
            AND name = #{name}
        </if>
    </where>
</select>

2. <choose>, <when>, <otherwise> 标签

这些标签类似于 Java 中的 switch 语句, <choose> 标签用于选择一个条件进行处理。

示例
<select id="selectUser" parameterType="map" resultType="User">
    SELECT * FROM users
    <where>
        <choose>
            <when test="id != null">
                AND id = #{id}
            </when>
            <when test="name != null">
                AND name = #{name}
            </when>
            <otherwise>
                AND email = #{email}
            </otherwise>
        </choose>
    </where>
</select>

3. <trim> 标签

<trim> 标签用于处理动态 SQL 中的冗余字符,如多余的空格、逗号、ANDOR 关键字等。它允许你在 SQL 语句的开头或结尾添加或移除某些内容,这对于构建更干净、更有效的 SQL 语句特别有用。<trim> 标签支持以下属性:

  1. prefix:
    这个属性允许你在 <trim> 标签内生成的 SQL 语句前添加一个前缀。例如,你可以添加 "WHERE""SET" 等关键词作为前缀。

  2. prefixOverrides:
    这个属性指定了应该从最终 SQL 语句中移除的前缀内容。通常用于移除可能由 <trim> 标签内其他元素(如 <if>)生成的多余字符。例如,如果你的 SQL 语句开始于多个 ANDOR,你可以设置 prefixOverrides="AND,OR" 来移除这些多余的关键词。

  3. suffix:
    这个属性允许你在 <trim> 标签内生成的 SQL 语句末尾添加一个后缀。例如,你可能会添加 ")", 等字符。

  4. suffixOverrides:
    类似于 prefixOverrides,这个属性指定了应该从最终 SQL 语句中移除的后缀内容。例如,如果你的 SQL 结尾有多余的逗号,你可以通过设置 suffixOverrides="," 来移除它。

下面是一个使用 <trim> 标签的示例,展示如何构建一个带有动态 WHERE 子句的 SQL 查询:

<select id="findUsers" parameterType="map" resultType="User">
  SELECT * FROM user
  <trim prefix="WHERE" prefixOverrides="AND|OR">
    <if test="userId != null">
      AND user_id = #{userId}
    </if>
    <if test="username != null">
      AND username = #{username}
    </if>
    <if test="email != null">
      AND email = #{email}
    </if>
  </trim>
</select>

在这个例子中:

  • prefix="WHERE" 将在动态条件开始时添加 WHERE 关键词。
  • prefixOverrides="AND|OR" 将确保如果动态条件为空,WHERE 关键词不会被添加,并且还会移除任何不必要的 ANDOR 关键词。

使用 <trim> 标签可以帮助你编写更加健壮和可读性强的动态 SQL 语句。

4. <where> 标签

<where> 标签用于自动添加 WHERE 关键字,并自动去掉多余的 ANDOR

⚠️注意:只会自动去掉多余的,并不会自动添加。
where的自动去掉多余AND是基于trim实现的
🤔️ 问题:trim支持添加前缀,为什么where却不支持自动添加呢?

正确示例
<select id="selectUser" parameterType="map" resultType="User">
    SELECT * FROM users
    <where>
        <if test="id != null">
            AND id = #{id}
        </if>
        <if test="name != null">
            AND name = #{name}
        </if>
    </where>
</select>
示例 - if中缺少AND

假设我们在 <if> 语句中没有包含 AND

<select id="selectUser" parameterType="map" resultType="User">
    SELECT * FROM users
    <where>
        <if test="id != null">
            id = #{id}
        </if>
        <if test="name != null">
            name = #{name}
        </if>
    </where>
</select>

如果 idname 都不为 null,生成的 SQL 语句会是:

SELECT * FROM users WHERE id = 1 name = 'John'

这会产生语法错误,因为 id = 1name = 'John' 之间缺少 AND

5. <set> 标签

<set> 标签用于动态生成 SET 语句,适用于 UPDATE 操作,并去掉多余的逗号。

示例
<update id="updateUser" parameterType="User">
    UPDATE users
    <set>
        <if test="name != null">
            name = #{name},
        </if>
        <if test="email != null">
            email = #{email},
        </if>
    </set>
    WHERE id = #{id}
</update>

6. 其他动态 SQL 标签

<foreach> 标签

<foreach> 标签用于处理集合类型的参数(如列表、数组),用于批量操作。

示例
<select id="selectUsers" parameterType="list" resultType="User">
    SELECT * FROM users WHERE id IN
    <foreach item="id" collection="list" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>
<bind> 标签

<bind> 标签用于创建一个新的绑定变量。

示例
<select id="selectUser" parameterType="string" resultType="User">
    <bind name="pattern" value="'%' + name + '%'" />
    SELECT * FROM users WHERE name LIKE #{pattern}
</select>

动态 SQL 示例

综合示例
<select id="selectUser" parameterType="map" resultType="User">
    SELECT * FROM users
    <where>
        <if test="id != null">
            AND id = #{id}
        </if>
        <if test="name != null">
            AND name = #{name}
        </if>
        <if test="email != null">
            AND email = #{email}
        </if>
    </where>
</select>

<update id="updateUser" parameterType="User">
    UPDATE users
    <set>
        <if test="name != null">
            name = #{name},
        </if>
        <if test="email != null">
            email = #{email},
        </if>
    </set>
    WHERE id = #{id}
</update>

动态SQL应用和常见问题

应用场景

  1. 条件筛选:根据用户输入的不同条件动态生成 WHERE 子句,仅包含实际需要的条件。例如,在进行查询时,只有当用户提供了搜索关键词时才添加相应的过滤条件。

  2. 分页查询:根据分页参数动态调整 LIMIT 或 OFFSET(或在 SQL Server 中的 TOP 和 OFFSET FETCH)子句,实现数据分页功能。

  3. 动态排序:根据用户选择的排序字段和顺序动态生成 ORDER BY 子句。

  4. 动态列选择:根据需求选择返回的列,而不是总是返回所有列,提高查询效率。

  5. IN 子句的动态生成:当需要在 IN 条件中包含一个可变数量的参数列表时,动态生成 IN 子句。

  6. 嵌套查询条件:根据条件是否满足,动态决定是否执行某个子查询或 JOIN 操作。

常见问题

  1. 性能问题:不当使用动态 SQL 可能导致生成的 SQL 语句过于复杂,影响数据库执行效率。特别是当使用过多的 <if><choose> 等标签时,生成的 SQL 可能难以被数据库优化。

  2. SQL 注入:虽然 MyBatis 自身对参数进行了预编译处理,减少了 SQL 注入的风险,但如果动态 SQL 逻辑错误地拼接字符串作为 SQL 参数,仍然可能引入安全漏洞。

  3. 可读性和维护性:过度使用动态 SQL 可能使映射文件变得复杂难懂,降低了代码的可读性和维护性。

  4. 类型转换错误:动态 SQL 中的条件判断可能涉及不同类型变量的比较,如果不注意类型转换,可能会遇到类型不匹配的问题。

  5. 标签使用不当:不正确使用 MyBatis 提供的动态 SQL 标签(如 <if><foreach><choose> 等)可能导致预期之外的 SQL 生成或执行错误。

为了避免这些问题,建议遵循以下最佳实践:

  • 尽量保持 SQL 的简洁和明确,避免不必要的复杂动态逻辑。
  • 使用预编译参数而非字符串拼接来防止 SQL 注入。
  • 适时对动态 SQL 进行单元测试和性能测试,确保其按预期工作且性能良好。
  • 在设计动态 SQL 时,保持映射文件的清晰和逻辑的可读性,必要时添加注释说明。
  • 熟悉并正确运用 MyBatis 提供的各种动态 SQL 标签。

mybatis 动态处理where源码

在 MyBatis 源代码中,<where> 标签的实现位于 org.apache.ibatis.scripting.xmltags 包中,特别是在 WhereSqlNode 类中。这个类负责处理 <where> 标签的具体逻辑。

MyBatis 源代码中相关类的位置

  1. WhereSqlNode:处理 <where> 标签的逻辑。

    • 包路径:org.apache.ibatis.scripting.xmltags
    • 类文件:WhereSqlNode.java
  2. TrimSqlNodeWhereSqlNode 类继承自 TrimSqlNode 类,TrimSqlNode 负责处理动态 SQL 中的前缀和后缀逻辑。

    • 包路径:org.apache.ibatis.scripting.xmltags
    • 类文件:TrimSqlNode.java

WhereSqlNode 类的代码

以下是 WhereSqlNode 类的简化代码示例:

package org.apache.ibatis.scripting.xmltags;

public class WhereSqlNode extends TrimSqlNode {
  
  private static List<String> prefixList = Arrays.asList("AND ","OR ","AND\n", "OR\n", "AND\r", "OR\r", "AND\t", "OR\t");

  public WhereSqlNode(Configuration configuration, SqlNode contents) {
    super(configuration, contents, "WHERE", PREFIX_LIST, null, null);
  }
}

WhereSqlNode 通过继承 TrimSqlNode 类,并传递 WHERE 作为前缀,自动处理多余的 ANDOR 关键字。

TrimSqlNode 类的代码

以下是 TrimSqlNode 类的简化代码示例:

package org.apache.ibatis.scripting.xmltags;

public class TrimSqlNode implements SqlNode {

  private final Configuration configuration;
  private final SqlNode contents;
  private final String prefix;
  private final List<String> prefixOverrides;

  public TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, List<String> prefixOverrides, String suffix, List<String> suffixOverrides) {
    this.configuration = configuration;
    this.contents = contents;
    this.prefix = prefix;
    this.prefixOverrides = prefixOverrides;
  }

  @Override
  public boolean apply(DynamicContext context) {
    FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
    boolean result = contents.apply(filteredDynamicContext);
    filteredDynamicContext.applyAll();
    return result;
  }

  private class FilteredDynamicContext extends DynamicContext {
    private DynamicContext delegate;
    private boolean prefixApplied;

    public FilteredDynamicContext(DynamicContext delegate) {
      super(configuration, delegate.getBindings());
      this.delegate = delegate;
    }

    public void applyAll() {
      if (prefix != null) {
        String sql = getSql();
        if (sql.length() > 0) {
          applyPrefix(sql);
        }
      }
    }

    private void applyPrefix(String sql) {
      if (!prefixApplied) {
        String trimmedSql = sql.trim();
        if (trimmedSql.length() > 0) {
          for (String prefixToOverride : prefixOverrides) {
            if (trimmedSql.toUpperCase().startsWith(prefixToOverride)) {
              trimmedSql = trimmedSql.substring(prefixToOverride.length());
              break;
            }
          }
          delegate.appendSql(prefix + " " + trimmedSql);
          prefixApplied = true;
        }
      }
    }
  }
}

TrimSqlNode 负责处理 SQL 语句的前缀和后缀逻辑,确保生成的 SQL 语句格式正确。

高级特性

一级缓存和二级缓存

MyBatis 提供了两级缓存机制:一级缓存(PerpetualCache)和二级缓存(Ehcache、Hazelcast等)。下面详细介绍这两级缓存的工作原理、源码分析以及配置,并使用 Mermaid 图标识缓存作用的位置。

一级缓存(PerpetualCache)

工作原理
  • 范围:一级缓存是 SqlSession 级别的缓存,即同一个 SqlSession 中的查询结果会被缓存,再次查询相同的数据会从缓存中获取。
  • 失效:当 SqlSession 执行 INSERTUPDATEDELETE 操作,或调用 clearCache() 方法时,一级缓存会被清空。
源码分析

一级缓存的实现类是 PerpetualCache,它被 CachingExecutor 用来缓存查询结果。

主要代码

PerpetualCache 的实现:

public class PerpetualCache implements Cache {
  private final String id;
  private final Map<Object, Object> cache = new HashMap<>();

  public PerpetualCache(String id) {
    this.id = id;
  }

  @Override
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }

  @Override
  public Object getObject(Object key) {
    return cache.get(key);
  }

  @Override
  public Object removeObject(Object key) {
    return cache.remove(key);
  }

  @Override
  public void clear() {
    cache.clear();
  }

  @Override
  public int getSize() {
    return cache.size();
  }
}

CachingExecutor 的部分实现:

public class CachingExecutor implements Executor {
  private final Executor delegate;
  private final Map<CacheKey, Object> localCache = new HashMap<>();

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    if (localCache.containsKey(key)) {
      return (List<E>) localCache.get(key);
    } else {
      List<E> result = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
      localCache.put(key, result);
      return result;
    }
  }

  @Override
  public void commit(boolean required) throws SQLException {
    delegate.commit(required);
    localCache.clear();
  }

  @Override
  public void rollback(boolean required) throws SQLException {
    delegate.rollback(required);
    localCache.clear();
  }
}
配置

一级缓存默认开启,无需额外配置。只要使用相同的 SqlSession,一级缓存就会自动生效。

二级缓存

工作原理
  • 范围:二级缓存是 Mapper 级别的缓存,即同一个 Mapper 的所有 SqlSession 共享这一级缓存。
  • 配置:需要在 MyBatis 配置文件中显式配置并开启。
  • 失效:当某个表的数据发生变更(INSERTUPDATEDELETE),相关缓存会失效。
源码分析

二级缓存的实现依赖于 Cache 接口和相应的实现类(如 EhcacheCacheRedisCache 等)。

主要代码

Cache 接口定义:

public interface Cache {
  void putObject(Object key, Object value);
  Object getObject(Object key);
  Object removeObject(Object key);
  void clear();
  int getSize();
}

配置文件中开启二级缓存:

<cache/>

示例:

<mapper namespace="com.example.mapper.UserMapper">
    <cache/>
    <select id="selectUser" parameterType="map" resultType="User">
        SELECT * FROM users WHERE id = #{id}
    </select>
</mapper>

二级缓存配置

在 MyBatis 全局配置文件(mybatis-config.xml)中配置缓存:

<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>

在每个 Mapper XML 文件中启用二级缓存:

<cache/>

图示

SqlSession
query
检查一级缓存
检查二级缓存
如果不存在
如果不存在
Application
Executor
CachingExecutor
PerpetualCache
Cache
Database
  • Application:应用程序调用 MyBatis。
  • SqlSession:MyBatis 会话。
  • Executor:执行器,负责执行 SQL 语句。
  • CachingExecutor:带缓存功能的执行器。
  • PerpetualCache:一级缓存,SqlSession 级别的缓存。
  • Cache:二级缓存,Mapper 级别的缓存。
  • Database:数据库,最终的数据源。

缓存生命周期&替换策略&持久化&使用注意

MyBatis 缓存的生命周期和持久化

一级缓存
  • 生命周期:一级缓存是基于 SqlSession 的。也就是说,它的生命周期和 SqlSession 是相同的。当 SqlSession 被关闭或清理时,一级缓存中的数据也会被清除。
  • 持久化:一级缓存不会持久化,它只存在于 SqlSession 的内存中。
二级缓存
  • 生命周期:二级缓存是基于 Mapper 的。只要 Mapper 存在,二级缓存的数据就会保留。
  • 持久化:二级缓存可以配置为持久化,这取决于缓存实现。如果使用的是像 Ehcache、Redis 等支持持久化的缓存实现,数据可以被持久化到磁盘或其他存储介质。

缓存替换策略

一级缓存
  • 一级缓存没有特别的替换策略,因为它的生命周期和 SqlSession 是相同的,一旦 SqlSession 关闭,缓存就会被清空。
二级缓存
  • MyBatis 本身没有定义具体的替换策略,而是依赖于具体的缓存实现。例如,使用 Ehcache 时,可以配置 LRU(最近最少使用)、LFU(最少频率使用)等策略。

配置二级缓存持久化的示例

使用 Ehcache
  1. 引入依赖

    <dependency>
        <groupId>org.mybatis.caches</groupId>
        <artifactId>mybatis-ehcache</artifactId>
        <version>1.1.0</version>
    </dependency>
    
  2. 配置 Ehcache
    ehcache.xml 文件中配置持久化和缓存策略:

    <ehcache>
        <diskStore path="java.io.tmpdir"/>
        <cache name="com.example.mapper.UserMapper"
               maxEntriesLocalHeap="1000"
               eternal="false"
               timeToIdleSeconds="300"
               timeToLiveSeconds="600"
               diskSpoolBufferSizeMB="20"
               maxEntriesLocalDisk="10000000"
               diskExpiryThreadIntervalSeconds="120"
               memoryStoreEvictionPolicy="LFU">
            <persistence strategy="localTempSwap"/>
        </cache>
    </ehcache>
    
  3. MyBatis 配置
    在 MyBatis 的全局配置文件 mybatis-config.xml 中启用二级缓存并指定缓存实现:

    <settings>
        <setting name="cacheEnabled" value="true"/>
    </settings>
    <typeAliases>
        <typeAlias alias="Ehcache" type="org.mybatis.caches.ehcache.EhcacheCache"/>
    </typeAliases>
    
  4. Mapper 配置
    在每个 Mapper XML 文件中启用二级缓存:

    <mapper namespace="com.example.mapper.UserMapper">
        <cache type="Ehcache"/>
        <select id="selectUser" parameterType="map" resultType="User">
            SELECT * FROM users WHERE id = #{id}
        </select>
    </mapper>
    

实际生产使用缓存的最佳实践

  1. 选择合适的缓存实现

    • 根据业务需求选择合适的缓存实现(如 Ehcache、Redis、Hazelcast 等)。
    • 确保缓存实现能满足系统的性能和持久化需求。
  2. 合理设置缓存的生命周期

    • 根据数据更新频率和访问频率设置合理的缓存失效时间。
    • 对于不常更新但访问频繁的数据,可以设置较长的失效时间。
  3. 避免缓存雪崩和击穿

    • 缓存雪崩:大量缓存同时失效导致数据库瞬间压力剧增。解决方案是为不同数据设置不同的过期时间。
    • 缓存击穿:某个热点数据在失效的瞬间,有大量请求同时访问该数据。解决方案是使用互斥锁或热点数据预热。
  4. 监控和优化缓存性能

    • 使用监控工具监控缓存的命中率、使用率等性能指标。
    • 根据监控结果不断优化缓存配置和使用策略。
  5. 数据一致性

    • 确保缓存中的数据与数据库中的数据一致性,特别是在数据更新时。
    • 可以使用消息队列或事件驱动机制来同步缓存和数据库的数据。

CacheBuilder

org.apache.ibatis.mapping.CacheBuilder 是 MyBatis 中用于构建缓存对象的类。它提供了一系列的参数和方法来配置缓存的行为。这些参数直接影响缓存的性能、持久化和替换策略等。以下是 CacheBuilder 的各个参数及其含义,并说明它们如何最终影响缓存。

CacheBuilder 各个参数的含义

package org.apache.ibatis.mapping;

import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.decorators.*;
import org.apache.ibatis.cache.impl.PerpetualCache;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.Alias;

import java.util.Properties;

public class CacheBuilder {
  private final String id;
  private Class<? extends Cache> implementation;
  private Class<? extends Cache> decorator;
  private Integer size;
  private Long clearInterval;
  private boolean readWrite;
  private Properties properties;
  private boolean blocking;

  public CacheBuilder(String id) {
    this.id = id;
    this.implementation = PerpetualCache.class;
    this.decorator = LruCache.class;
  }

  public CacheBuilder implementation(Class<? extends Cache> implementation) {
    this.implementation = implementation;
    return this;
  }

  public CacheBuilder addDecorator(Class<? extends Cache> decorator) {
    this.decorator = decorator;
    return this;
  }

  public CacheBuilder size(Integer size) {
    this.size = size;
    return this;
  }

  public CacheBuilder clearInterval(Long clearInterval) {
    this.clearInterval = clearInterval;
    return this;
  }

  public CacheBuilder readWrite(boolean readWrite) {
    this.readWrite = readWrite;
    return this;
  }

  public CacheBuilder blocking(boolean blocking) {
    this.blocking = blocking;
    return this;
  }

  public CacheBuilder properties(Properties properties) {
    this.properties = properties;
    return this;
  }

  public Cache build() {
    setDefaultImplementations();
    Cache cache = newBaseCacheInstance(implementation, id);
    setCacheProperties(cache);
    if (PerpetualCache.class.equals(cache.getClass())) {
      for (Class<? extends Cache> decorator : Arrays.asList(LruCache.class, SoftCache.class, WeakCache.class, ScheduledCache.class, SerializedCache.class, LoggingCache.class, SynchronizedCache.class, TransactionalCache.class, BlockingCache.class)) {
        if (decorator.equals(this.decorator)) {
          cache = newCacheDecoratorInstance(decorator, cache);
          setCacheProperties(cache);
        }
      }
    }
    return cache;
  }

  private void setDefaultImplementations() {
    if (implementation == null) {
      implementation = PerpetualCache.class;
      if (decorator == null) {
        decorator = LruCache.class;
      }
    }
  }

  private Cache newBaseCacheInstance(Class<? extends Cache> cacheClass, String id) {
    Constructor<? extends Cache> cacheConstructor = cacheClass.getConstructor(String.class);
    return cacheConstructor.newInstance(id);
  }

  private void setCacheProperties(Cache cache) {
    if (properties != null) {
      MetaObject metaCache = SystemMetaObject.forObject(cache);
      for (Object key : properties.keySet()) {
        String propertyName = (String) key;
        String value = properties.getProperty(propertyName);
        metaCache.setValue(propertyName, value);
      }
    }
  }

  private Cache newCacheDecoratorInstance(Class<? extends Cache> cacheClass, Cache base) {
    Constructor<? extends Cache> cacheConstructor = cacheClass.getConstructor(Cache.class);
    return cacheConstructor.newInstance(base);
  }
}

各个参数的含义

  1. id:缓存的唯一标识符,一般是 Mapper 的命名空间。
  2. implementation:缓存的基础实现类,默认是 PerpetualCache。该类提供最基本的缓存功能。
  3. decorator:缓存装饰器类,用于增强基础缓存的功能,如 LRU、FIFO 等。默认是 LruCache
  4. size:缓存大小,指定缓存能够存储的对象数量上限。适用于基于容量限制的缓存策略。
  5. clearInterval:缓存清理间隔时间,以毫秒为单位。某些缓存实现可能会定期清理过期或不再使用的缓存项。
  6. readWrite:指定缓存是否为可读写模式。可读写模式下,缓存存储的是对象的序列化副本,以防止脏读。
  7. properties:缓存配置的属性。可以为缓存的实现类或装饰器传递特定的配置信息。
  8. blocking:指定缓存是否为阻塞模式。在阻塞模式下,当缓存中没有命中时,其他线程会等待当前线程加载完毕再返回。

缓存的影响

这些参数会直接影响缓存的行为和性能。例如:

  • 缓存实现和装饰器:决定了缓存的基础功能和增强功能,如是否使用 LRU 策略、软引用缓存等。
  • 缓存大小:控制缓存的容量,避免内存溢出,但也可能导致频繁的缓存替换。
  • 清理间隔:控制缓存的刷新频率,平衡缓存的实时性和性能。
  • 读写模式:影响缓存的一致性和性能,读写模式可能导致较高的序列化和反序列化开销。
  • 阻塞模式:防止缓存穿透,但可能导致线程阻塞,影响系统的并发性能。

实际生产使用缓存的最佳实践

  1. 选择合适的缓存实现

    • 根据业务需求选择适合的缓存实现和装饰器组合,如 LruCacheEhcacheRedisCache 等。
  2. 配置合理的缓存大小

    • 根据系统的内存情况和数据访问频率设置缓存大小,避免缓存过大导致内存不足或过小导致缓存命中率低。
  3. 设置合适的清理间隔

    • 根据数据更新频率和业务需求设置清理间隔,确保缓存中的数据足够新鲜且不过于频繁清理。
  4. 使用读写模式

    • 在需要防止脏读的场景下使用读写模式,但要注意序列化和反序列化的性能开销。
  5. 启用阻塞模式

    • 在高并发访问且缓存穿透可能导致数据库压力过大的场景下使用阻塞模式,但要注意可能的线程阻塞问题。

自定义缓存

在 MyBatis 中,可以通过实现自定义的缓存来扩展其缓存机制。默认情况下,MyBatis 使用的是基于 PerpetualCache 的一级缓存和基于 LruCache 的二级缓存。如果需要自定义缓存,可以按照以下步骤进行:

实现自定义缓存

  1. 创建自定义缓存类

    首先,需要创建一个类来实现自定义的缓存,该类需要继承自 org.apache.ibatis.cache.Cache 接口。

    public class MyCustomCache implements Cache {
    
        private final String id;
    
        public MyCustomCache(String id) {
            this.id = id;
        }
    
        @Override
        public String getId() {
            return id;
        }
    
        @Override
        public void putObject(Object key, Object value) {
            // 实现缓存对象的放置逻辑
            // 示例:将 key-value 对放入自定义的缓存中
        }
    
        @Override
        public Object getObject(Object key) {
            // 实现从缓存中获取对象的逻辑
            // 示例:根据 key 从自定义缓存中获取对象
            return null;
        }
    
        @Override
        public Object removeObject(Object key) {
            // 实现从缓存中移除对象的逻辑
            // 示例:根据 key 从自定义缓存中移除对象
            return null;
        }
    
        @Override
        public void clear() {
            // 清空缓存
            // 示例:清空自定义缓存中的所有对象
        }
    
        @Override
        public int getSize() {
            // 获取缓存中存储对象的数量
            // 示例:返回自定义缓存中对象的数量
            return 0;
        }
    
        @Override
        public ReadWriteLock getReadWriteLock() {
            // 返回用于控制缓存的读写锁
            // 示例:返回一个自定义的读写锁
            return null;
        }
    }
    

    在实现缓存类时,需要根据实际需求实现 putObjectgetObjectremoveObjectclear 等方法来管理缓存中的对象,并且可以选择是否实现 getReadWriteLock 方法来支持并发控制。

  2. 配置 MyBatis 使用自定义缓存

    在 MyBatis 的配置文件 mybatis-config.xml 中配置使用自定义缓存:

    <cache type="com.example.MyCustomCache"/>
    

    这里的 com.example.MyCustomCache 是你实现的自定义缓存类的完整路径名。

  3. 注册自定义缓存(可选):

    如果不在 XML 中配置,还可以在 Java 代码中注册自定义缓存:

    // 在代码中注册自定义缓存
    Configuration configuration = new Configuration();
    configuration.addCache(new MyCustomCache("com.example.MyCustomCache"));
    

注意事项

  • 并发控制:自定义缓存需要考虑并发访问时的线程安全性,可以使用合适的读写锁来管理缓存的并发访问。
  • 缓存清理策略:需要根据业务需求实现缓存对象的清理策略,以控制缓存对象的数量和存储时长。
  • 性能考虑:自定义缓存的实现需要考虑性能,尽量避免实现过于复杂或低效的缓存逻辑,以保证系统的性能表现。

分页查询

在 MyBatis 中实现分页查询主要有两种方式:使用 RowBounds 对象和利用分页插件。

1. 使用 RowBounds

RowBounds 是 MyBatis 提供的一个简单分页参数对象,它通过在查询时传入 RowBounds 参数来实现物理分页。RowBounds 包含两个属性:offsetlimit,分别表示从第几条记录开始查询以及需要查询多少条记录。

示例

List<User> users = sqlSession.selectList("com.example.mapper.UserMapper.selectAllUsers", null, new RowBounds(10, 20));

在这个例子中,将会从第 11 条记录开始(索引从0开始),查询20条用户记录。

缺点

  • RowBounds 的分页逻辑实现在客户端,需要一次性查询所有数据到内存中,然后再进行截取,当数据量大时,可能会造成内存溢出。
  • 它没有利用数据库的分页功能,性能较差。

2. 使用分页插件

为了更高效地实现分页,推荐使用分页插件。MyBatis 提供了一个官方推荐的分页插件 PageHelper,但请注意,PageHelper 并不是 MyBatis 核心的一部分,而是作为一个外部插件存在。

安装与配置
首先,需要在项目中引入 PageHelper 的依赖。然后,在 MyBatis 的配置文件中添加插件配置。

示例

在 Mapper 接口中定义方法:

List<User> selectAllUsers(@Param("name") String name, Page page);

在 Service 层使用:

PageHelper.startPage(pageNum, pageSize); // pageNum 是当前页码,pageSize 是每页大小
List<User> users = userMapper.selectAllUsers(null);
PageInfo<User> pageInfo = new PageInfo<>(users);

优点

  • 直接在 SQL 语句中生成 LIMIT 或 ROW_NUMBER 等分页关键字,利用数据库的分页能力,减少数据传输量,提升性能。
  • 配置简单,易于使用。

缺点

  • 需要引入额外的依赖。
  • 对于某些特定的数据库方言支持可能需要额外配置。

PageHelper默认排序字段&增删数据后如何避免数据重复读

PageHelper 默认情况下并不指定排序字段,这意味着如果你不显式地在SQL查询中指定ORDER BY子句,那么分页查询的结果可能是无序的。因此,为了保证分页数据的连贯性和避免前后两页数据出现重叠或混乱,你应该明确指定一个或多个排序字段。

例如,如果你想要按照ID字段升序排序,你可以这样做:

PageHelper.orderBy("id ASC");
List<User> users = userMapper.selectAllUsers();

确保每次分页查询时都应用了相同的排序规则,这样即使数据中有增删,只要排序字段和顺序保持一致,相邻页码之间的数据应该是连续且不重叠的。

对于数据增删导致的分页问题,具体来说:

  • 数据删除:如果在查询第一页后,数据集中有记录被删除,但这些记录不在第一页显示范围内,那么第一页的数据不会变化,后续页面的记录会自然前移填补空缺,由于有排序保证,不会出现重复数据。
  • 数据插入:新插入的数据,如果其排序字段的值位于已查询页之间,理论上新数据可能会影响到前后两页的边界。为了精确控制分页,特别是在实时性要求高的场景,可以考虑以下策略:
    • 在查询下一页之前,依据上一页最后一条记录的排序字段值作为查询条件,比如 where id > lastIdFromPreviousPage,这样可以确保新插入的数据不会导致前后页数据重叠。
    • 如果使用的是偏移量分页(即 LIMIT offset, limit),并且无法避免数据重叠,可能需要在获取新页数据后在客户端去重。

多表关联映射方式

MyBatis 是一个支持自定义 SQL、存储过程以及高级映射的持久层框架,它提供了多种方式来映射一对一、一对多和多对多关系。下面是一些常用的映射方式:

一对一关系映射

假设有两个表 authorbook,一个作者可以写多本书,但每本书只能有一个作者。

  1. 嵌套查询(Nested Queries):使用嵌套查询来映射一对一关系。

    <!-- 在 Author 的映射文件中 -->
    <resultMap id="authorMap" type="Author">
        <id property="id" column="author_id"/>
        <result property="name" column="author_name"/>
        <result property="bio" column="author_bio"/>
        <result property="book" column="book_id" select="selectBookById"/>
    </resultMap>
    
    <select id="selectAuthorById" resultMap="authorMap">
        SELECT author_id, author_name, author_bio, book_id
        FROM author
        WHERE author_id = #{id}
    </select>
    
    <!-- 在 Book 的映射文件中 -->
    <resultMap id="bookMap" type="Book">
        <id property="id" column="book_id"/>
        <result property="title" column="book_title"/>
        <result property="isbn" column="book_isbn"/>
        <!-- other properties -->
    </resultMap>
    
    <select id="selectBookById" resultMap="bookMap">
        SELECT book_id, book_title, book_isbn
        FROM book
        WHERE book_id = #{book_id}
    </select>
    

    authorMap 中,使用 select="selectBookById" 来指定嵌套查询,通过 book_id 来关联作者和书籍信息。

  2. 嵌套结果(Nested Results):将书籍信息直接嵌套在作者的查询结果中。

    <resultMap id="authorMap" type="Author">
        <id property="id" column="author_id"/>
        <result property="name" column="author_name"/>
        <result property="bio" column="author_bio"/>
        <association property="book" column="book_id" javaType="Book">
            <id property="id" column="book_id"/>
            <result property="title" column="book_title"/>
            <result property="isbn" column="book_isbn"/>
        </association>
    </resultMap>
    

一对多关系映射

假设有两个表 departmentemployee,一个部门可以有多个员工。

  1. 集合映射(Collection Mapping):使用集合来映射一对多关系。

    <resultMap id="departmentMap" type="Department">
        <id property="id" column="dept_id"/>
        <result property="name" column="dept_name"/>
        <collection property="employees" ofType="Employee" column="dept_id" select="selectEmployeesByDeptId"/>
    </resultMap>
    
    <select id="selectDepartmentById" resultMap="departmentMap">
        SELECT dept_id, dept_name
        FROM department
        WHERE dept_id = #{id}
    </select>
    
    <resultMap id="employeeMap" type="Employee">
        <id property="id" column="emp_id"/>
        <result property="name" column="emp_name"/>
        <!-- other properties -->
    </resultMap>
    
    <select id="selectEmployeesByDeptId" resultMap="employeeMap">
        SELECT emp_id, emp_name
        FROM employee
        WHERE dept_id = #{dept_id}
    </select>
    

    departmentMap 中使用 collection 元素来指定集合映射,通过 dept_id 关联部门和员工信息。

多对多关系映射

假设有两个表 studentcourse,一个学生可以选修多门课程,一门课程也可以被多个学生选修。

  1. 中间表映射(Intermediate Table Mapping):使用中间表来映射多对多关系。

    <resultMap id="studentMap" type="Student">
        <id property="id" column="student_id"/>
        <result property="name" column="student_name"/>
        <collection property="courses" ofType="Course" column="course_id" select="selectCoursesByStudentId"/>
    </resultMap>
    
    <select id="selectStudentById" resultMap="studentMap">
        SELECT student_id, student_name
        FROM student
        WHERE student_id = #{id}
    </select>
    
    <resultMap id="courseMap" type="Course">
        <id property="id" column="course_id"/>
        <result property="name" column="course_name"/>
        <!-- other properties -->
    </resultMap>
    
    <select id="selectCoursesByStudentId" resultMap="courseMap">
        SELECT c.course_id, c.course_name
        FROM course c
        JOIN student_course sc ON c.course_id = sc.course_id
        WHERE sc.student_id = #{student_id}
    </select>
    

    studentMap 中使用 collection 元素来指定多对多关系的映射,通过中间表 student_course 来关联学生和课程信息。

resultMap处理复杂结果映射

在 MyBatis 中,<resultMap> 标签被用来描述如何将查询结果映射到 Java 对象,特别是当涉及到复杂的映射关系,如一对一、一对多或多对多关联时。下面是如何使用 <resultMap> 定义复杂结果集映射的几个关键点:

基本结构

<resultMap id="resultMapId" type="com.example.YourEntityClass">
    <!-- 结果映射条目 -->
</resultMap>
  • id 属性是 resultMap 的唯一标识符,用于在其他地方引用。
  • type 属性指定这个结果映射将应用于哪个 Java 类。

常用映射条目

  1. <id>: 用于映射主键字段。
  2. <result>: 用于映射普通字段。
  3. 集合映射:
    • <association>: 用于一对一关联映射。
    • <collection>: 用于一对多或多对一关联映射。

一对一关联()

假设有一个 User 类和一个 Address 类,一个 User 有一个 Address

<resultMap id="UserResultMap" type="com.example.User">
    <id property="id" column="user_id"/>
    <result property="username" column="username"/>
    <association property="address" javaType="com.example.Address" resultMap="AddressResultMap"/>
</resultMap>

<resultMap id="AddressResultMap" type="com.example.Address">
    <id property="id" column="address_id"/>
    <result property="city" column="city"/>
    <result property="street" column="street"/>
</resultMap>

一对多关联()

如果一个 User 可以有多个 Order,则可以使用 <collection>

<resultMap id="UserWithOrdersResultMap" type="com.example.User">
    <id property="id" column="user_id"/>
    <result property="username" column="username"/>
    <collection property="orders" ofType="com.example.Order" resultMap="OrderResultMap"/>
</resultMap>

<resultMap id="OrderResultMap" type="com.example.Order">
    <id property="id" column="order_id"/>
    <result property="productName" column="product_name"/>
    <result property="amount" column="amount"/>
</resultMap>

复杂情况下的映射技巧

  • 嵌套 <association><collection>:可以在 <association><collection> 内部继续嵌套其他 <association><collection>,以处理更深层次的关联关系。
  • 使用 <discriminator> 进行类型区分:当一个结果集中可能映射到多个 Java 类型时,可以使用 <discriminator> 根据某个字段的值来决定具体映射到哪个类。
  • 使用 <select> 子查询:在 <association><collection> 中,可以使用 <select> 子元素执行一个单独的 SQL 查询来获取关联对象,这称为延迟加载。

discriminator

在 MyBatis 中,<discriminator> 元素用于处理继承关系的类映射,它可以根据某个字段的值来决定实例化哪个子类。下面是一个使用 <discriminator> 的示例,假设我们有一个基类 Person 和两个子类 StudentTeacher,并且数据库中有一个 person 表,其中有一个字段 type 用于区分学生和教师。

数据库表结构示例

CREATE TABLE person (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    age INT,
    type CHAR(1) CHECK (type IN ('S', 'T')) -- S 代表学生, T 代表教师
    -- 其他字段...
);

Java 类结构

public class Person {
    private int id;
    private String name;
    private int age;
    // getters & setters
}

public class Student extends Person {
    private String studentId;
    // getters & setters
}

public class Teacher extends Person {
    private String subject;
    // getters & setters
}

MyBatis 映射文件

Person 的 resultMap 中使用 <discriminator> 来区分 StudentTeacher

<resultMap id="PersonResultMap" type="com.example.Person">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <result property="age" column="age"/>
    <discriminator javaType="char" column="type">
        <case value="S" resultMap="StudentResultMap"/>
        <case value="T" resultMap="TeacherResultMap"/>
    </discriminator>
</resultMap>

<resultMap id="StudentResultMap" type="com.example.Student" extends="PersonResultMap">
    <result property="studentId" column="student_id"/>
</resultMap>

<resultMap id="TeacherResultMap" type="com.example.Teacher" extends="PersonResultMap">
    <result property="subject" column="subject"/>
</resultMap>

解释

  • PersonResultMap 是基类映射,它包含所有共有的字段映射,并通过 <discriminator> 根据 type 字段区分不同类型的实体。
  • <case> 标签指定了不同的类型值应该映射到哪个子类的 resultMap。例如,当 type 为 ‘S’ 时,MyBatis 将使用 StudentResultMap 进行进一步的映射;为 ‘T’ 时,则使用 TeacherResultMap
  • extends="PersonResultMap" 表示 StudentResultMapTeacherResultMap 继承自 PersonResultMap,这样就无需在每个子类映射中重复定义公共字段。

批量操作

在 MyBatis 中实现批量插入、更新和删除操作涉及到一些特定的技巧和注意事项,具体方法如下:

批量插入

  1. 使用 <insert> 标签和 <foreach> 标签

    <insert id="batchInsert" parameterType="java.util.List">
        INSERT INTO my_table (column1, column2, ...)
        VALUES
        <foreach collection="list" item="item" separator=",">
            (#{item.property1}, #{item.property2}, ...)
        </foreach>
    </insert>
    
    • parameterType 设置为 java.util.List 或者对应的集合类型。
    • 使用 <foreach> 标签遍历列表,生成批量插入的 SQL。
  2. 注意事项

    • 数据库驱动和 MyBatis 版本需要支持 JDBC 的批处理。
    • 尽量控制批量插入的数量,避免一次性插入过多数据,影响性能。

批量更新

  1. 使用 <update> 标签和 <foreach> 标签

    <update id="batchUpdate" parameterType="java.util.List">
        <foreach collection="list" item="item" separator=";">
            UPDATE my_table
            SET column1 = #{item.property1}, column2 = #{item.property2}, ...
            WHERE id = #{item.id}
        </foreach>
    </update>
    
    • parameterType 设置为 java.util.List 或者对应的集合类型。
    • 使用 <foreach> 标签遍历列表,生成批量更新的 SQL。
  2. 注意事项

    • 更新语句需要确保每条记录都有唯一的条件(例如 WHERE id = #{item.id}),否则会导致意外更新或执行失败。

批量删除

  1. 使用 <delete> 标签和 <foreach> 标签

    <delete id="batchDelete" parameterType="java.util.List">
        DELETE FROM my_table
        WHERE id IN
        <foreach collection="list" item="item" open="(" close=")" separator=",">
            #{item.id}
        </foreach>
    </delete>
    
    • parameterType 设置为 java.util.List 或者对应的集合类型。
    • 使用 <foreach> 标签遍历列表,生成批量删除的 SQL。
  2. 注意事项

    • 删除操作也需要确保每条记录有明确的删除条件,避免意外删除。
    • 如果涉及到级联删除或者依赖约束,需要谨慎处理批量删除的顺序和条件。

注意事项总结:

  • 参数类型:使用 java.util.List 或者对应的集合类型作为参数类型。
  • SQL 生成:使用 <foreach> 标签遍历集合,生成对应的批量操作 SQL。
  • 性能影响:控制每次批量操作的数量,以免影响数据库性能。
  • 事务管理:在执行批量操作时,通常需要考虑事务的管理,保证操作的原子性和一致性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/765459.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MySQL:设计数据库与操作

设计数据库 1. 数据建模1.1 概念模型1.2 逻辑模型1.3 实体模型主键外键外键约束 2. 标准化2.1 第一范式2.2 链接表2.3 第二范式2.4 第三范式 3. 数据库模型修改3.1 模型的正向工程3.2 同步数据库模型3.3 模型的逆向工程3.4 实际应用建议 4. 数据库实体模型4.1 创建和删除数据库…

10.8K star!史上最强Web应用防火墙雷池WAF

长亭雷池SafeLine是长亭科技耗时近 10 年倾情打造的WAF(Web Application Firewall)&#xff0c; 一款敢打出口号 “不让黑客越雷池一步” 的 WAF&#xff0c;愿称之为史上最强的一款Web应用防火墙&#xff0c;足够简单、足够好用、足够强的免费且开源的 WAF&#xff0c;基于业…

leetcode-20-回溯-切割、子集

一、[131]分割回文串 给定一个字符串 s&#xff0c;将 s 分割成一些子串&#xff0c;使每个子串都是回文串。 返回 s 所有可能的分割方案。 示例: 输入: "aab" 输出: [ ["aa","b"], ["a","a","b"] ] 分析&…

JAVA连接FastGPT实现流式请求SSE效果

FastGPT 是一个基于 LLM 大语言模型的知识库问答系统&#xff0c;提供开箱即用的数据处理、模型调用等能力。同时可以通过 Flow 可视化进行工作流编排&#xff0c;从而实现复杂的问答场景&#xff01; 一、先看效果 真正实流式请求&#xff0c;SSE效果&#xff0c;SSE解释&am…

一切为了安全丨2024中国应急(消防)品牌巡展武汉站成功召开!

消防品牌巡展武汉站 6月28日&#xff0c;由中国安全产业协会指导&#xff0c;中国安全产业协会应急创新分会、应急救援产业网联合主办&#xff0c;湖北消防协会协办的“一切为了安全”2024年中国应急(消防)品牌巡展-武汉站成功举办。该巡展旨在展示中国应急&#xff08;消防&am…

Python基础002

Python数据类型 1、字符串&#xff08;str&#xff09; str3 """I miss you so much""" print("str3 ", str3,type(str3)) str3 I miss you so much <class str>2、整数&#xff08;int&#xff09; str1 55 print(&quo…

【面试题】TLS和SSL协议的区别

TLS&#xff08;Transport Layer Security&#xff09;和SSL&#xff08;Secure Sockets Layer&#xff09;协议都是用于在网络上建立安全通信连接的协议&#xff0c;但它们在多个方面存在区别。以下是TLS和SSL协议之间区别的详细分析&#xff1a; 1. 发展历程与标准化 SSL&a…

如何找BMS算法、BMS软件的实习

之前一直忙&#xff0c;好久没有更新了&#xff0c;今天就来写一篇文章来介绍如何找BMS方向的实习&#xff0c;以及需要具备哪些条件&#xff0c;我的实习经历都是在读研阶段找的&#xff0c;读研期间两段的实习经历再加上最高影响因子9.4分的论文&#xff0c;我的秋招可以说是…

分子AI预测赛Task2笔记

下面所述比较官方的内容都来自官方文档 ‍‌⁠‌‍​​​‌​​⁠​​​​​&#xfeff;​​​&#xfeff;‍‬​​‍⁠‍‍​​‬​&#xfeff;‌​​​‌‍‬​​​​​​‍‌Task2&#xff1a;赛题深入解析 - 飞书云文档 (feishu.cn) 赛题背景 强调了人工智能在科研领域&…

探囊取物之多形式注册页面(基于BootStrap4)

基于BootStrap4的注册页面&#xff0c;支持手机验证码注册、账号密码注册 低配置云服务器&#xff0c;首次加载速度较慢&#xff0c;请耐心等候&#xff1b;演练页面可点击查看源码 预览页面&#xff1a;http://www.daelui.com/#/tigerlair/saas/preview/ly4gax38ub9j 演练页…

晚上睡觉要不要关路由器?一语中的

前言 前几天小白去了一个朋友家&#xff0c;有朋友说&#xff1a;路由器不关机的话会影响睡眠吗&#xff1f; 这个影响睡眠嘛&#xff0c;确实是会的。毕竟一时冲浪一时爽&#xff0c;一直冲浪一直爽……刷剧刷抖音刷到根本停不下来&#xff0c;肯定影响睡眠。 所以晚上睡觉要…

MQTT协议详述

MQTT 概述 消息队列遥测传输&#xff08;英语&#xff1a;Message Queuing Telemetry Transport&#xff0c;缩写&#xff1a;MQTT&#xff09;&#xff0c;是基于发布&#xff08;Publish&#xff09;/订阅&#xff08;Subscribe&#xff09;范式的消息协议&#xff0c;位于…

BurpSuite抓IOS设备HTTPS流量

一、简述&#xff1a; Burp 这个工具做过 web 安全的人都应该用过&#xff0c;是个非常强大的抓包工具。在 PC 的浏览器上直接配置代理就行了&#xff0c;本篇文章就来介绍一下如何用 Burp 抓 IOS 设备上的流量&#xff0c;很多文章都介绍过怎么抓包&#xff0c;但是很多坑都没…

Linux驱动开发实战宝典:设备模型、模块编程、I2C/SPI/USB外设精讲

摘要: 本文将带你走进 Linux 驱动开发的世界,从设备驱动模型、内核模块开发基础开始,逐步深入 I2C、SPI、USB 等常用外设的驱动编写,结合实际案例,助你掌握 Linux 驱动开发技能。 关键词: Linux 驱动,设备驱动模型,内核模块,I2C,SPI,USB 一、Linux 设备驱动模型 Li…

java反射和注解

反射 获取class对象的三种方法 ①&#xff1a;Class.forName("全类名"); ②&#xff1a;类名.class ③&#xff1a;对象.getclass(); 代码样例 package com.ithema;public class Main {public static void main(String[] args) throws ClassNotFoundException {//第…

【JavaEE精炼宝库】多线程进阶(2)synchronized原理、JUC类——深度理解多线程编程

一、synchronized 原理 1.1 基本特点&#xff1a; 结合上面的锁策略&#xff0c;我们就可以总结出&#xff0c;synchronized 具有以下特性(只考虑 JDK 1.8)&#xff1a; 开始时是乐观锁&#xff0c;如果锁冲突频繁&#xff0c;就转换为悲观锁。 开始是轻量级锁实现&#xff…

QT+winodow 代码适配调试总结(二)

已经好多年了&#xff0c; linux环境下不同版本的QT程序开发和部署&#xff0c;突然需要适配window环境程序调试&#xff0c;一堆大坑&#xff0c;还真是一个艰巨的任务&#xff0c;可是kpi下的任务计划&#xff0c;开始吧&#xff01;&#xff01; 1、首先我们自定义的动态库…

【STM32HAL库学习】通信方式:USART、IIC、SPI

通信的目的&#xff1a;将一个设备的数据传送到另一个设备&#xff0c;扩展硬件系统 通信接口区别 名称引脚双工时钟电平设备USARTTX、RX全双工异步单端点对点I2CSCL、SDA半双工同步单端多设备SPISCLK、MOSI、MISO、CS全双工同步单端多设备CANCAN_H、CAN_L半双工异步差分多设…

【数值计算库-超长笔记】Python-Mpmath库:高精度数值计算

原文链接&#xff1a;https://www.cnblogs.com/aksoam/p/18279394 更多精彩&#xff0c;关注博客园主页&#xff0c;不断学习&#xff01;不断进步&#xff01; 我的主页 csdn很少看私信&#xff0c;有事请b站私信 博客园主页-发文字笔记-常用 有限元鹰的主页 内容&#xf…

类与对象完结

1.匿名类 先看一个例子 class A { public:A(int a 0):_a(a){cout << "A(int a)" << endl;}~A(){cout << "~A()" << endl;}void Print(){cout << _a << endl;} private:int _a; };int main() {A aa1(10);aa1.Print(…