Mybatis

1.什么是 MyBatis?#

  • MyBatis 是一款优秀的持久层框架(持久层,可以理解成数据 保存在 数据库或者 硬盘一类可以保存很长时间的设备里面).
  • 它支持自定义 SQL、存储过程以及高级映射。
  • MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

1.2 持久化#

内存:断电即失

所以持久化 能把数据保存在磁盘上面

1.3 持久层#

完成持久化工作的代码块

1.4 为什么要用Mybatis#

JDBC代码太复杂,框架更简单

  • 简单易学:
  • 灵活:
  • 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
  • 提供映射标签,支持对象与数据库的orm字段关系映射
  • 提供对象关系映射标签,支持对象关系组建维护
  • 提供xml标签,支持编写动态sql。

2.第一个Mybatis程序#

思想#

2.1搭建数据库#

use mybatis;
CREATE TABLE user(

	id INT PRIMARY KEY,
	name VARCHAR(20),
	pwd VARCHAR(20)
	
)ENGINE=INNODB DEFAULT charset=utf8;

INSERT INTO `user`(NAME,pwd) VALUES
("chen1","123456"),
("chen2","123456"),
("chen3","123456")

2.2 配置mybatis#

1.mybatis配置文件 mybatis-config.xml#

作用: SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
    -----------注册-------------
  <mappers>
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>

2.构建sqlSessionFactory对象#

作用:获取SqlSession对象(SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句)

public class SqlSeeionUtils {

    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
   public static SqlSessionFactory getSqlSessionFactory(){
        return sqlSessionFactory;
    }
}

public class UserDaoTest {

    @Test
   public void test1(){
        SqlSessionFactory sqlSessionFactory = SqlSeeionUtils.getSqlSessionFactory();
       //获得sqlSession对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
       //sqlSession对象 获得数操作SQL语句操作对象 Mapper
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        List<User> allUsers = mapper.getAllUsers();
        for (User allUser : allUsers) {
            System.out.println(allUser);
        }
    }

}

4.SqlSession对象#

  • 增删改 必须需要事务,默认自动提交事务关闭的,需要在sqlSessionFactory.openSession(true);开启

    ​ sqlSession.commit();

5.映射器.xml文件和 dao接口#

通过 id = dao接口方法名 和 namespace 连接起来

<mapper namespace="com.cb.dao.UserDao">
    <select id="getAllUsers" resultType="com.cb.pojo.User">
    select * from user
  </select>
</mapper>

3.xml配置文件#

  • configuration(配置)
    • properties(属性)
    • settings(设置)
    • typeAliases(类型别名)
    • ​ typeHandlers(类型处理器)
    • ​ objectFactory(对象工厂)
    • ​ plugins(插件)
    • environments(环境配置)
      • environment(环境变量)
      • transactionManager(事务管理器)
      • dataSource(数据源)
    • ​ databaseIdProvider(数据库厂商标识)
    • mappers(映射器)

environments(环境配置)#

<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    </transactionManager>
    <dataSource type="POOLED">
      <property name="driver" value="${driver}"/>
      <property name="url" value="${url}"/>
      <property name="username" value="${username}"/>
      <property name="password" value="${password}"/>
    </dataSource>
  </environment>
</environments>

MyBatis 可以配置成适应多种环境,不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

如果忽略了环境参数,那么将会加载默认环境.

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);

事务管理器(transactionManager)#

在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):

  • JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
<transactionManager type="MANAGED">
  <property name="closeConnection" value="false"/>
</transactionManager>

如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。

数据源(dataSource)#

dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。

有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):

添加其他数据源

<dataSource type="org.myproject.C3P0DataSourceFactory">
  <property name="driver" value="org.postgresql.Driver"/>
  <property name="url" value="jdbc:postgresql:mydb"/>
  <property name="username" value="postgres"/>
  <property name="password" value="root"/>
</dataSource>

属性(properties)#

<properties resource="db.properties">

</properties>
后面的就可以使用 ${driver}去引用
优先使用resource="db.properties"导入的 

类型别名(typeAliases)#

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:

单个
<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
</typeAliases>

使用包
<typeAliases>
  <package name="domain.blog"/>
</typeAliases>

使用注解
@Alias("author")
public class Author {
    ...
}

设置(settings)#

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项设置的含义、默认值等。

设置名 描述 有效值 默认值
cacheEnabled 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 true | false true
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 true | false false
logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING 未设置
mapUnderscoreToCamelCase 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 true | false False

映射器(mappers)注册#

<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>

//需要在同一个包下面,且接口和.xml文件同名
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

4.xml映射器#

参数#

parameterType 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
#{},${} #{}防止SQL注入,
@Param("") 定义别名,给传参数用。

参数是map#

 SqlSessionFactory sqlSessionFactory = SqlSeeionUtils.getSqlSessionFactory();
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserDao mapper = sqlSession.getMapper(UserDao.class);
//        List<User> allUsers = mapper.getAllUsers();
//        for (User allUser : allUsers) {
//            System.out.println(allUser);
//        }
        HashMap<String, Object> map = new HashMap<>();
        map.put("userName","aaa");
        map.put("password","21321");
        mapper.addUser(map);
        sqlSession.commit();
        sqlSession.close();


 <insert id="addUser" parameterType="map">
        insert into mybatis.user(name,pwd) values (#{userName},#{password})
 </insert>

结果映射resultMap#

解决 数据库字段 和 属性 不一致的问题 ,可以把数据库查询出来的数据进行封装成pojo。

resultType只是 简单的数据返回

1.解决字段名和属性名不一致#

-----------取别名-----

<select id="getAllUsers" resultType="com.cb.pojo.User">
select id,name,pwd as password from user
</select>

-----------结果集映射-----
 <resultMap id="Users" type="com.cb.pojo.User">
        <result column="pwd" property="password"/>
  </resultMap>


2.一对多和多对一#

返回映射是一个对象用 association

返回映射是一个集合用 collection

多对一:多个学生对应一个老师,即

public class Student {
    private int id;
    private String name;
    private Teacher teacher;
}
<resultMap id="stu" type="com.cb.pojo.Student">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <association property="teacher" javaType="com.cb.pojo.Teacher">
        <id property="id" column="tid"/>
        <result property="name" column="tName"/>
    </association>
</resultMap>
<select id="getAllStus" resultMap="stu">
    select t.id as tid ,t.name as tName ,s.id,s.name from  teacher t   left join  student s on s.tid = t.id;
</select>

一对多:一个老师对应多个学生,即

@Data
public class Teacher {
    private int id;
    private String name;

    private List<Student> stus;
}
  <resultMap id="tea" type="com.cb.pojo.Teacher">
        <id column="tid" property="id"/>
        <result property="name" column="tname"/>

        <collection property="stus" ofType="com.cb.pojo.Student">
            <id column="sid" property="id"/>
            <result property="name" column="sname"/>
        </collection>
    </resultMap>

    <select id="getAllTea" resultMap="tea">
        select t.id tid ,t.name tname ,s.id sid ,s.name sname ,s.tid stid
from teacher t left join student s
on s.tid = t.id;
    </select>

5.日志#

用来记录 SQL操作

mybatis 默认提供

logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING 未设置
  • SLF4J (掌握)

  • LOG4J

  • LOG4J2

  • JDK_LOGGING

  • COMMONS_LOGGING

  • STDOUT_LOGGING 标准输出

    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    
    控制台 显示
    Created connection 1166151249.
    Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@45820e51]
    ==>  Preparing: select * from user 
    ==> Parameters: 
    <==    Columns: id, name, pwd
    <==        Row: 1, chen1, 123456
    <==        Row: 2, chen2, 123456
    <==        Row: 3, chen3, 123456
    <==        Row: 5, aaa, 21321
    <==      Total: 4
    
    
  • NO_LOGGING

LOG4J#

  • 控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;

  • 控制每一条日志的输出格式;

  • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程

  • 通过一个配置文件来灵活地进行配置

导入log4j的包

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

配置文件log4j.properties#

#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file

#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=【%c】-%m%n

#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/kuang.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=【%p】【%d{yy-MM-dd}】【%c】%m%n

#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

使用log4j#

Logger logger = Logger.getLogger(UserDaoTest.class);
logger.info("info------------");
logger.error("info------------");
logger.debug("info------------");

-------------显示 文件和控制台----------
[INFO][20-07-29][com.cb.dao.UserDaoTest]info------------
[ERROR][20-07-29][com.cb.dao.UserDaoTest]info------------
[DEBUG][20-07-29][com.cb.dao.UserDaoTest]info------------

6.分页#

1.使用limit进行分页#

<select id="getUserBylimit" resultType="com.cb.pojo.User" parameterType="map">
    select * from user limit #{indexStart} , #{pageSize}
</select>

2.pageHelper插件#

1. 使用 Maven#

在 pom.xml 中添加如下依赖:

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>最新版本</version>
</dependency>

最新版本号可以从首页查看。

2. 配置拦截器插件#

特别注意,新版拦截器是 com.github.pagehelper.PageInterceptorcom.github.pagehelper.PageHelper 现在是一个特殊的 dialect 实现类,是分页插件的默认实现类,提供了和以前相同的用法。

1. 在 MyBatis 配置 xml 中配置拦截器插件

<!--
    plugins在配置文件中的位置必须符合要求,否则会报错,顺序如下:
    properties?, settings?,
    typeAliases?, typeHandlers?,
    objectFactory?,objectWrapperFactory?,
    plugins?,
    environments?, databaseIdProvider?, mappers?
-->
<plugins>
    <!-- com.github.pagehelper为PageHelper类所在包名 -->
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <!-- 使用下面的方式配置参数,后面会有所有的参数介绍 -->
        <property name="param1" value="value1"/>
	</plugin>
</plugins>

3. 分页插件参数介绍

3.代码中使用#

//第三种,Mapper接口方式的调用,推荐这种使用方式。
PageHelper.offsetPage(1, 10);
List<Country> list = countryMapper.selectIf(1);

7.注解开发#

面向接口开发#

代码实现#

package org.mybatis.example;
public interface BlogMapper {
  @Select("SELECT * FROM blog WHERE id = #{id}")
  Blog selectBlog(int id);
}

8.Lombok#

Lombok项目是一个Java库,它会自动插入编辑器和构建工具中,Lombok提供了一组有用的注释,用来消除Java类中的大量样板代码。仅五个字符(@Data)就可以替换数百行代码从而产生干净,简洁且易于维护的Java类。

常用注解:

@常用注解:
@Setter :注解在类或字段,注解在类时为所有字段生成setter方法,注解在字段上时只为该字段生成setter方法。
@Getter :使用方法同上,区别在于生成的是getter方法。
@ToString :注解在类,添加toString方法。
@EqualsAndHashCode: 注解在类,生成hashCode和equals方法。
@NoArgsConstructor: 注解在类,生成无参的构造方法。
@RequiredArgsConstructor: 注解在类,为类中需要特殊处理的字段生成构造方法,比如final和被@NonNull注解的字段。
@AllArgsConstructor: 注解在类,生成包含类中所有字段的构造方法。
@Data: 注解在类,生成setter/getter、equals、canEqual、hashCode、toString方法,如为final属性,则不会为该属性生成setter方法。
@Slf4j: 注解在类,生成log变量,严格意义来说是常量。

9.动态SQL#

能够根据不同的条件 构建出 不同的SQL语句

if#

判断条件是否满足 ,满足就使用。

<select id="getIf" resultType="com.cb.pojo.Blog" parameterType="map" >
    select * from blog where 1=1
    <if test="title != null">
        and title = #{title}
    </if>

    <if test="title != null">
        and author= #{author}
    </if>
</select>

判断不为null 且 不为空字符串#

<if test="keyword != null and keyword != ''">
        AND  user_id = #{keyword} 
</if>

choose (when, otherwise)#

从几个条件选择 一个条件出来:

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

trim (where, set)#

where 标签where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG
  <where>
    <if test="state != null">
         state = #{state}
    </if>
    <if test="title != null">
        AND title like #{title}
    </if>
    <if test="author != null and author.name != null">
        AND author_name like #{author.name}
    </if>
  </where>
</select>

set 跟 update一起用

<update id="updateAuthorIfNecessary">
  update Author
    <set>
      <if test="username != null">username=#{username},</if>
      <if test="password != null">password=#{password},</if>
      <if test="email != null">email=#{email},</if>
      <if test="bio != null">bio=#{bio}</if>
    </set>
  where id=#{id}
</update>

trim标签:用来重新定义set 和 where标签

<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

foreach#

动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。比如:

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  WHERE ID in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>

你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

bind#

bind 元素允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文。比如:

<select id="selectBlogsLike" resultType="Blog">
  <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" /> 
  SELECT * FROM BLOG
  WHERE title LIKE #{pattern}
</select>

 _parameter代表那个参数(传递过来的参数)   '%' + #{name} + '%'  _parameter.getName()

案例:

<select id="getUserByName" resultType="com.cb.pojo.User" parameterType="string" >
    <bind name="name1" value="'%'+_parameter+'%'"/>
    select * from user
    where name like #{name1}

</select>

传递多个参数#

//模糊查询
    List<User> likeByNameOrIponeCode(@Param("name") String name,@Param("ipone") String iponeCode);



 <select id="likeByNameOrIponeCode" parameterType="string" resultType="com.cb.pojo.User">
        <bind name="_name" value=" '%'+ name +'%'"/>
        <bind name="_ipone" value=" '%' + ipone  +'%'"/>
        SELECT * from t_user
        <where>
            <if test=" name != null and name!=''">
                username like  #{_name}
            </if>
            <if test="ipone != null and ipone!='' ">
                or phoneCode like #{_ipone}
            </if>
        </where>

    </select>

10.缓存#

作用:

  • 把查询出来的数据 缓存到内存里面

  • 再次使用时,就可以不用从关系数据库里面查询,提高查询效率。

  • 一般缓存不经常改变的数据

  • 所有缓存都放在以及缓存,当SqlSession关闭时,才会转移到二级缓存

mybatis缓存#

  • 默认开启一级缓存

一级缓存#

  Mybatis对缓存提供支持,但是在没有配置的默认情况下,它只开启一级缓存,一级缓存只是相对于同一个SqlSession而言。所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。

            

  为什么要使用一级缓存,不用多说也知道个大概。但是还有几个问题我们要注意一下。

  1、一级缓存的生命周期有多长?

每一个请求都都会 开启一个一个新的SqlSession对象

  a、MyBatis在开启一个数据库会话时,会 创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象。Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。

  b、如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。

  c、如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用。

  d、SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用

二级缓存:#

  MyBatis的二级缓存是namespece级别的缓存,它可以提高对数据库查询的效率,以提高应用的性能。

​ 意思是 新来的请求也可以拿到缓存,如果在同一个Mapper下

MyBatis的缓存机制整体设计以及二级缓存的工作模式#

  SqlSessionFactory层面上的二级缓存默认是不开启的,二级缓存的开席需要进行配置,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的。 也就是要求实现Serializable接口,配置方法很简单,只需要在映射XML文件配置就可以开启缓存了,如果我们配置了二级缓存就意味着:

  • 映射语句文件中的所有select语句将会被缓存。
  • 映射语句文件中的所欲insert、update和delete语句会刷新缓存。
  • 缓存会使用默认的Least Recently Used(LRU,最近最少使用的)算法来收回。
  • 根据时间表,比如No Flush Interval,(CNFI没有刷新间隔),缓存不会以任何时间顺序来刷新。
  • 缓存会存储列表集合或对象(无论查询方法返回什么)的1024个引用
  • 缓存会被视为是read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以安全的被调用者修改,不干扰其他调用者或线程所做的潜在修改。

#

作者:Esofar

出处:https://www.cnblogs.com/firsthelloworld/p/13399570.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   我不想学编丿程  阅读(318)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示