当前位置: 首页 > news >正文

MyBatis(二)

一、MyBatis 连接池技术详解

1.1 连接池基础概念

连接池是存储数据库连接的容器,通过复用连接避免频繁创建/销毁连接的开销,显著提升程序性能。

MyBatis 内置三种连接池类型,通过 dataSource 标签的 type 属性配置:

  • UNPOOLED:不使用连接池,每次请求创建新连接(性能较差,适用于简单场景)。
  • POOLED:使用内置连接池,自动管理连接的创建、复用和销毁(推荐生产环境使用)。
  • JNDI:通过 JNDI(Java 命名目录接口)获取容器管理的连接池(适用于 Java EE 容器环境)。

1.2 核心配置示例(POOLED 连接池)

<environments default="mysql"><environment id="mysql"><transactionManager type="JDBC"/><dataSource type="POOLED"><!-- 数据库驱动与连接信息 --><property name="driver" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></dataSource></environment>
</environments>

二、动态 SQL 实战:从条件拼接 to 复杂查询

2.1 if 标签:条件动态拼接

场景:根据用户输入的条件动态拼接 SQL 查询语句,避免硬编码。
示例:根据用户名和性别筛选用户:

<select id="findByWhere" parameterType="User" resultType="User">SELECT * FROM user<where><if test="username != null and username != ''">AND username LIKE #{username}</if><if test="sex != null and sex != ''">AND sex = #{sex}</if></where>
</select>
  • where 标签作用:自动处理首个 AND 关键字,避免 WHERE 1=1 冗余写法。

测试

public interface UserMapper {// 条件查询public List<User> findByWhere(User user);}
	/*** 条件查询* @throws IOException*/@Testpublic void testFindByWhere() throws IOException {// 先加载主配置文件,加载到输入流中InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");// 创建SqlSessionFactory对象,创建SqlSession对象SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);// 创建SqlSession对象SqlSession session = factory.openSession();User user = new User();user.setUsername("%熊%");user.setSex("女");// 条件查询UserMapper mapper = session.getMapper(UserMapper.class);List<User> list = mapper.findByWhere(user);for (User user1 : list) {System.out.println(user1);}// 关闭资源session.close();inputStream.close();}

2.2 foreach 标签:批量操作与集合查询

场景:处理 IN 条件、批量插入/删除等集合操作。

2.2.1 场景一:IN 条件查询
<select id="findByIds" parameterType="User" resultType="User">SELECT * FROM user<where><foreach collection="ids" open="id in ( " separator="," close=")" item="i">#{i}</foreach></where>
</select>
  • 参数传递:通过 User 对象的 List<Integer> ids 属性传递待查询的 ID 集合。
  • 关键属性
    • collection:集合属性名(如 ids)。
    • item:集合元素别名(如 i)。
    • open/close:拼接 SQL 的前缀和后缀(如 IN ())。
    • separator:元素间分隔符(如 ,)。
2.2.2 场景二:批量插入
<insert id="batchInsert" parameterType="List<User>">INSERT INTO user (username, sex)VALUES<foreach collection="list" item="u" separator=",">(#{u.username}, #{u.sex})</foreach>
</insert>

2.3 公用 SQL 提取:sql 与 include 标签

场景:重复 SQL 片段提取,提升代码复用性。
示例:提取基础查询语句:

<!--提取公共的SQL-->
<sql id="findAllSql">select * from user
</sql><select id="findAll" resultType="User"><include refid="findAllSql"/>
</select><select id="findById" parameterType="int" resultType="User"><include refid="findAllSql"/> WHERE id = #{id}
</select>

三、高级映射:从一对一到多对多

搭建开发环境

CREATE TABLE `user` (`id` int(11) NOT NULL auto_increment,`username` varchar(32) NOT NULL COMMENT '用户名称',`birthday` datetime default NULL COMMENT '生日',`sex` char(1) default NULL COMMENT '性别',`address` varchar(256) default NULL COMMENT '地址',PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;insert  into `user`(`id`,`username`,`birthday`,`sex`,`address`) values (1,'老王','2018-02-27 17:47:08','男','北京'),(2,'熊大','2018-03-02 15:09:37','女','上海'),(3,'熊二','2018-03-04 11:34:34','女','深圳'),(4,'光头强','2018-03-04 12:04:06','男','广州');CREATE TABLE `account` (`ID` int(11) NOT NULL COMMENT '编号',`UID` int(11) default NULL COMMENT '用户编号',`MONEY` double default NULL COMMENT '金额',PRIMARY KEY  (`ID`),KEY `FK_Reference_8` (`UID`),CONSTRAINT `FK_Reference_8` FOREIGN KEY (`UID`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;insert  into `account`(`ID`,`UID`,`MONEY`) values (1,1,1000),(2,2,1000),(3,2,2000);

3.1 多对一查询(Account → User)

场景:查询账户信息时关联用户数据(如账户所属用户的名称和地址)。

3.1.1 实体类定义
public class Account implements Serializable{private Integer id;private Integer uid; // 关联用户 IDprivate Double money;private User user; // 多对一关联的用户对象public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public Integer getUid() {return uid;}public void setUid(Integer uid) {this.uid = uid;}public Double getMoney() {return money;}public void setMoney(Double money) {this.money = money;}public User getUser() {return user;}public void setUser(User user) {this.user = user;}
}
3.1.2 AccountMapper 接口
public interface AccountMapper {public List<Account> findAll();}
3.1.3 Mapper 配置(使用 association 标签)
<mapper namespace="AccountMapper"><!--内连接查询--><select id="findAll" resultMap="accountMap">select a.*,u.username,u.address from account a,user u where a.uid = u.id</select><!--进行数据封装--><resultMap id="accountMap" type="Account"><result property="id" column="id"/><result property="uid" column="uid"/><result property="money" column="money"/><!-- 配置多对一关联 --><association property="user" javaType="User"><result property="username" column="username"/><result property="address" column="address"/></association></resultMap></mapper>
3.1.4 测试方法
	/*** 多对一查询* @throws IOException*/@Testpublic void testFindAll() throws IOException {// 先加载主配置文件,加载到输入流中InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");// 创建SqlSessionFactory对象,创建SqlSession对象SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);// 创建SqlSession对象SqlSession session = factory.openSession();// 获取代理对象AccountMapper mapper = session.getMapper(AccountMapper.class);// 查询List<Account> list = mapper.findAll();// 遍历for (Account account : list) {System.out.println(account);}// 关闭资源session.close();inputStream.close();}

3.2 一对多查询(User → Account)

场景:查询用户信息时关联其所有账户数据。

3.2.1 实体类定义
public class User implements Serializable{// 主键private Integer id;// 用户名private String username;// 生日private Date birthday;// 性别private String sex;// 地址private String address;// 一对多关联的账户列表private List<Account> accounts; public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public Date getBirthday() {return birthday;}public void setBirthday(Date birthday) {this.birthday = birthday;}public String getSex() {return sex;}public void setSex(String sex) {this.sex = sex;}public String getAddress() {return address;}public void setAddress(String address) {this.address = address;}@Overridepublic String toString() {return "User{" +"id=" + id +", username='" + username + '\'' +", birthday=" + birthday +", sex='" + sex + '\'' +", address='" + address + '\'' +", accounts=" + accounts +'}';}
}
3.2.2 UserMapper 接口
public interface UserMapper {//查询一对多public List<User> findOneToMany();}
3.2.3 Mapper 配置(使用 collection 标签)
<mapper namespace="UserMapper"><select id="findOneToMany" resultMap="userMap">select u.*,a.money from user u left join account a on u.id = a.uid</select><resultMap id="userMap" type="User"><result property="id" column="id"/><result property="username" column="username"/><result property="birthday" column="birthday"/><result property="sex" column="sex"/><result property="address" column="address"/><!-- 配置一对多关联 --><collection property="accounts" ofType="Account"><result property="money" column="money"/></collection></resultMap>
</mapper>
3.2.4 测试方法
/*** 一对多查询*/@Testpublic void testOneToMany() throws IOException {// 先加载主配置文件,加载到输入流中InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");// 创建SqlSessionFactory对象,创建SqlSession对象SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);// 创建SqlSession对象SqlSession session = factory.openSession();UserMapper mapper = session.getMapper(UserMapper.class);List<User> list = mapper.findOneToMany();// 遍历list集合for (User user : list) {System.out.println(user);}// 关闭资源session.close();inputStream.close();}

3.3 多对多查询(User ↔ Role)

场景:用户与角色为多对多关系(如用户可拥有多个角色,角色可分配给多个用户)。

3.3.1 表结构
  • user 表、role 表、中间表 user_role(存储用户与角色关联关系)。
CREATE TABLE `role` (`ID` int(11) NOT NULL COMMENT '编号',`ROLE_NAME` varchar(30) default NULL COMMENT '角色名称',`ROLE_DESC` varchar(60) default NULL COMMENT '角色描述',PRIMARY KEY  (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;insert  into `role`(`ID`,`ROLE_NAME`,`ROLE_DESC`) values (1,'组长','管理整个组'),(2,'班主任','管理整个班级'),(3,'校长','管理整个学校');CREATE TABLE `user_role` (`UID` int(11) NOT NULL COMMENT '用户编号',`RID` int(11) NOT NULL COMMENT '角色编号',PRIMARY KEY  (`UID`,`RID`),KEY `FK_Reference_10` (`RID`),CONSTRAINT `FK_Reference_10` FOREIGN KEY (`RID`) REFERENCES `role` (`ID`),CONSTRAINT `FK_Reference_9` FOREIGN KEY (`UID`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;insert  into `user_role`(`UID`,`RID`) values (1,1),(1,2),(2,2);
3.3.2 实体类定义
package com.qcby.domain;import java.io.Serializable;
import java.util.Date;
import java.util.List;public class User implements Serializable{private static final long serialVersionUID = 525400707336671154L;private Integer id;private String username;private Date birthday;private String sex;private String address;// 定义ids属性,用来存储所有的idprivate List<Integer> ids;// 演示一对多查询private List<Account> accounts;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public Date getBirthday() {return birthday;}public void setBirthday(Date birthday) {this.birthday = birthday;}public String getSex() {return sex;}public void setSex(String sex) {this.sex = sex;}public String getAddress() {return address;}public void setAddress(String address) {this.address = address;}public List<Integer> getIds() {return ids;}public void setIds(List<Integer> ids) {this.ids = ids;}public List<Account> getAccounts() {return accounts;}public void setAccounts(List<Account> accounts) {this.accounts = accounts;}
}public class Role implements Serializable{private static final long serialVersionUID = 4836306672907553166L;private Integer id;private String role_name;private String role_desc;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getRole_name() {return role_name;}public void setRole_name(String role_name) {this.role_name = role_name;}public String getRole_desc() {return role_desc;}public void setRole_desc(String role_desc) {this.role_desc = role_desc;}
}
3.3.3 RoleMapper接口
public interface RoleMapper {public List<Role> findAll();}
3.3.4 RoleMapper 配置
	<!-- RoleMapper配置 --><select id="findAll" resultMap="roleMap">SELECT r.*,u.username FROM USER u,user_role ur,role r WHERE u.id = ur.UID AND ur.RID = r.ID</select><resultMap type="role" id="roleMap"><id property="id" column="id"/><result property="role_name" column="role_name"/><result property="role_desc" column="role_desc"/><collection property="users" ofType="user"><result property="username" column="username"/> </collection></resultMap>
3.3.5 测试方法
	/*** 多对多查询*/@Testpublic void testRole() throws IOException{// 先加载主配置文件,加载到输入流中InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");// 创建SqlSessionFactory对象,创建SqlSession对象SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);// 创建SqlSession对象SqlSession session = factory.openSession();RoleDao dao=session.getMapper(RoleDao.class);List<Role> list = dao.findAll();for(Role role:list){System.out.println(role);}//关闭资源session.close();inputStream.close();}

四、延迟加载策略:按需加载关联数据

4.1 延迟加载 vs 立即加载

  • 立即加载:主查询时立即加载关联数据(如查询用户时同时查询其所有账户)。
  • 延迟加载:主查询时不加载关联数据,仅在访问关联数据时触发查询(提升初始查询性能)。

4.2 配置延迟加载

  1. 全局配置SqlMapConfig.xml):
<settings><setting name="lazyLoadingEnabled" value="true"/> <!-- 开启延迟加载 --><setting name="aggressiveLazyLoading" value="false"/> <!-- 关闭积极加载(按需加载) -->
</settings>
  1. 关联查询配置(以多对一为例):
<mapper namespace="AccountMapper"><select id="findAll" resultMap="accountMap">SELECT * FROM account <!-- 主查询仅查账户数据 --></select><resultMap id="accountMap" type="Account"><!-- 延迟加载用户数据,通过 select 指定关联查询方法,column 传递关联字段 --><association property="user" javaType="User" select="UserMapper.findById" column="uid"/></resultMap>
</mapper><mapper namespace="UserMapper"><select id="findById" parameterType="int" resultType="User">SELECT * FROM user WHERE id = #{id} <!-- 延迟加载时触发此查询 --></select>
</mapper>

注:

<configuration><!-- 注意:以下元素必须按此顺序排列 --><properties .../><settings .../><typeAliases .../><typeHandlers .../><objectFactory .../><objectWrapperFactory .../><reflectorFactory .../><plugins .../><environments .../><databaseIdProvider .../><mappers .../>
</configuration>

4.3 测试

4.3.1 多对一的延迟加载查询演示
  1. 在AccountMapper接口中编写方法
public List<Account> findAll();
  1. 编写配置文件和SQL语句
<?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="cn.tx.mapper.AccountMapper"><!-- 内连接的查询 --><select id="findAll" resultMap="accountMap">SELECT * from account</select><!-- 通过用户的id查询账户信息 --><select id="findByUid" parameterType="int" resultType="account">select * from account where uid = #{uid}</select><!-- 配置映射 --><resultMap type="Account" id="accountMap"><id column="id" property="id"/><result column="uid" property="uid"/><result column="money" property="money"/><!-- 配置延迟加载 --><association property="user" javaType="User" select="com.qcbyjy.mapper.UserMapper.findById" column="uid"><id column="id" property="id"/><result column="username" property="username"/><result column="birthday" property="birthday"/><result column="sex" property="sex"/><result column="addresss" property="addresss"/></association></resultMap></mapper>
  1. 在UserMapper接口中编写方法
public User findById(Integer uid);
  1. 编写配置文件
	<select id="findById" parameterType="int" resultType="User">select * from user where id = #{id}</select>
  1. 在SqlMapConfig.xml配置文件中开启延迟加载的配置
	<settings><!-- 开启延迟加载 --><setting name="lazyLoadingEnabled" value="true"/><!-- 将积极加载改为消极加载及按需加载 --><setting name="aggressiveLazyLoading" value="false"/></settings>
  1. 编写测试方法
	/*** 测试延迟加载的测试方法*/// 1.测试多对一的延迟加载的测试方法@Testpublic void testfindAccountlazyALL(){//加载配置文件try {InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");//创建sqlSessionFactory工厂对象  使用对象创建SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);//通过工厂对象在获得sqlsession对象SqlSession session = factory.openSession();//SqlSession session = factory.openSession(true);//获取到代理对象,MyBatis框架生成的代理对象AccountMapper mapper = session.getMapper(AccountMapper.class);//mapper指向的就是代理对象List<Account> list = mapper.findAll();session.commit();for (Account account : list) {System.out.println(account.getMoney());System.out.println(account.getUser().getUsername());System.out.println("=============================");System.out.println("");}//关闭资源session.close();inputStream.close();} catch (Exception e) {e.printStackTrace();}}
4.3.2 一对多的延迟加载查询演示
  1. 在UserMapper中编写方法
public interface UserMapper {public List<User> findAll();}
  1. 在UserMapper.xml配置文件中编写配置和SQL语句
<?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="cn.tx.mapper.UserMapper"><!-- 一对多的查询 --><select id="findAll" resultMap="userMap">select * from user</select><!-- 数据封装 --><resultMap type="user" id="userMap"><id column="id" property="id"/><result column="username" property="username"/><result column="birthday" property="birthday"/><result column="sex" property="sex"/><result column="addresss" property="addresss"/><!-- select=""           使用帐户的方法查询column="id"         使用id值去查询账户--><collection property="accounts" ofType="Account" select="cn.tx.mapper.AccountMapper.findByUid" column="id" ><id column="id" property="id"/><result column="uid" property="uid"/><result column="money" property="money"/></collection></resultMap></mapper>
  1. 在AccountMapper接口中编写方法
public List<Account> findByUid(Integer uid);
  1. 在AccountMapper.xml配置文件中编写配置和SQL语句
	<!-- 通过用户的id查询账户信息 --><select id="findByUid" parameterType="int" resultType="account">select * from account where uid = #{uid}</select>
  1. 在SqlMapConfig.xml配置文件中开启延迟加载的配置
	<settings><!-- 开启延迟加载 --><setting name="lazyLoadingEnabled" value="true"/><!-- 将积极加载改为消极加载及按需加载 --><setting name="aggressiveLazyLoading" value="false"/></settings>
  1. 编写测试方法
	// 2.测试一对多的延迟加载的测试方法@Testpublic void testfindUserlazyALL(){//加载配置文件try {InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");//创建sqlSessionFactory工厂对象  使用对象创建SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);//通过工厂对象在获得sqlsession对象SqlSession session = factory.openSession();//SqlSession session = factory.openSession(true);//获取到代理对象,MyBatis框架生成的代理对象UserMapper mapper = session.getMapper(UserMapper.class);//mapper指向的就是代理对象List<User> list = mapper.findAll();session.commit();for (User user : list) {System.out.println(user.getUsername());System.out.println(user.getAccounts().size());System.out.println("=============================");System.out.println("");}//关闭资源session.close();inputStream.close();} catch (Exception e) {e.printStackTrace();}}

五、MyBatis 缓存机制:提升查询性能

缓存在内存中临时存储数据,速度快,可以减少数据库的访问次数;经常需要查询,不经常修改的数据,不是特别重要的数据都适合于存储到缓存中。

5.1 一级缓存(SqlSession 缓存)

  • 作用域:基于 SqlSession 对象,默认开启。
  • 原理:首次查询后将结果存入 SqlSession 的本地缓存,相同查询(相同 SQL 与参数)再次执行时直接从缓存获取。
  • 触发更新:调用 update/insert/deletecommit/close 时清空缓存。

示例:验证一级缓存

/*** 演示一级缓存的存在*/
public class Test03 {/*** 证明一级缓存的存在* 一级缓存底层是Map集合* Map<k,v> k指SQL语句 v指查询出来的值*/@Testpublic void run1() throws IOException {//加载配置文件InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");//通过流创建工厂对象SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);//获取到sqlsession对象,没有设置事务的方式,默认手动提交SqlSession sqlSession = factory.openSession();//获取代理对象UserMapper mapper = sqlSession.getMapper(UserMapper.class);//调用方法 通过主键查询//先查询一级缓存,没有数据。会查数据库,都会有SQL语句,把查询出的数据存储到一级缓存中User user = mapper.findById(1);//打印地址System.out.println(user);System.out.println("==================================");//手动清空缓存//如果没有清空缓存,在执行第一条结束后,sql语句将会存放到mybatis提供的缓存中,两条查询语句将会执行一条;//若清空缓存,则将执行两条sql语句sqlSession.clearCache();//再查询一次//先查询一级缓存,存在数据。从缓存中把数据返回,就没有SQL语句User user1=mapper.findById(1);//这样两个user对象地址一样System.out.println(user1);sqlSession.close();inputStream.close();}
}

5.2 二级缓存(全局缓存)

  • 作用域:基于 SqlSessionFactory,跨 SqlSession 共享缓存。
  • 配置步骤
    1. 开启全局缓存SqlMapConfig.xml):
      <settings><setting name="cacheEnabled" value="true"/> <!-- 开启二级缓存 -->
      </settings>
      
    2. 在 Mapper 中配置缓存
      <mapper namespace="UserMapper"><!-- 开启二级缓存使用 --><cache/><!-- 配置缓存策略 <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/> --><select id="findById" resultType="User" useCache="true">SELECT * FROM user WHERE id = #{id}</select>
      </mapper>
      
    3. 实体类实现序列化接口
      public class User implements Serializable { /* ... */ }
      

5.3 缓存策略对比

缓存类型作用域时效性适用场景
一级缓存SqlSession会话周期内有效单会话内重复查询
二级缓存SqlSessionFactory全局有效(需手动配置)跨会话重复查询、读多写少场景

六、总结:MyBatis 核心能力与最佳实践

  1. 连接池选择:生产环境优先使用 POOLED 连接池,提升连接复用效率。
  2. 动态 SQL 原则
    • 复杂条件拼接用 if+where,集合查询用 foreach
    • 提取公用 SQL 片段,避免重复编码。
  3. 关联映射策略
    • 多对一/一对一用 association,一对多/多对多用 collection
    • 大数据量场景优先使用延迟加载,减少初始查询压力。
  4. 缓存优化
    • 合理使用一级缓存(默认开启,无需额外配置)。
    • 二级缓存适用于高频查询、低频更新场景,需注意实体类序列化与缓存策略配置。

通过掌握上述核心技术,可在实际项目中高效使用 MyBatis 完成复杂数据操作,平衡性能与开发效率。

相关文章:

  • AI Agent开发第70课-彻底消除RAG知识库幻觉(4)-解决知识库问答时语料“总重复”问题
  • 生成树的保护机制
  • 解决 Tailwind CSS 代码冗余问题
  • 功能安全管理
  • ES(ES2023/ES14)最新更新内容,及如何减少内耗
  • 《C++与OpenCV实战:图像增强大核心算法详解》​​
  • 设备预测性维护:从技术架构到工程实践,中讯烛龙如何实现停机时间锐减
  • 玄机-第二章日志分析-redis应急响应
  • Eigen与OpenCV矩阵操作全面对比:最大值、最小值、平均值
  • 时序数据库、实时数据库与实时数仓:如何为实时数据场景选择最佳解决方案?
  • 模拟电路中的电感:从“电磁倔驴“到“电路稳定器“的奇幻漂流
  • TYUT-企业级开发教程-第二章
  • MinIO:从入门到精通,解锁云原生存储的奥秘
  • Linux下 使用 SSH 完成 Git 绑定 GitHub
  • 广域网学习
  • Flink 的水印机制
  • AI大模型学习二十六、使用 Dify + awesome-digital-human-live2d + ollama + ChatTTS打造数字人
  • nginx概念及使用
  • Python format()函数高级字符串格式化详解
  • 字节跳动开源通用图像定制模型DreamO,支持风格转换、换衣、身份定制、多条件组合等多种功能~
  • “大国重器”、新型反隐身雷达……世界雷达展全面展示尖端装备
  • 2024年全国博物馆接待观众14.9亿人次
  • 一旅客因上错车阻挡车门关闭 ,株洲西高铁站发布通报
  • 穆迪下调美国主权信用评级
  • 科普|认识谵妄:它有哪些表现?患者怎样走出“迷雾”?
  • 陕西三原高新区违法占用土地,被自然资源局罚款10万元