Mybatis-基础操作

                Mybatis的基础操作就是通过Mybatis完成对数据的增删改查。我们通过例子来引入这些操作,之前的项目较久远,因此我们从零开始进行准备工作:

搭建项目

一、创建数据库user_list并插入数据:

-- 创建数据库
create table user_list
(id          int unsigned primary key auto_increment comment '主键ID',username    varchar(100) comment '用户名',password    varchar(32) default '123456' comment '密码',gender      tinyint unsigned comment '性别,1男2女',job         tinyint unsigned comment '部门ID',creat_time  datetime not null comment '创建时间',update_time datetime not null comment '修改时间'
) comment '基础信息表';
INSERT INTO user_list (username, password, gender, job, creat_time, update_time)
VALUES ('张三', 'zhangsan_pwd123', 1, 1, NOW() - INTERVAL 14 DAY, NOW()),('李四', 'lisi_secure_pwd', 2, 2, NOW() - INTERVAL 13 DAY, NOW()),('王五', 'wangwu_123456', 1, 3, NOW() - INTERVAL 12 DAY, NOW()),('赵六', 'zhaoliu_password', 2, 4, NOW() - INTERVAL 11 DAY, NOW()),('孙七', 'sunqi_pwd_2023', 1, 5, NOW() - INTERVAL 10 DAY, NOW()),('周八', 'zhouba_secure', 2, 1, NOW() - INTERVAL 9 DAY, NOW()),('吴九', 'wujiu_123', 1, 2, NOW() - INTERVAL 8 DAY, NOW()),('郑十', 'zhengshi_pwd', 2, 3, NOW() - INTERVAL 7 DAY, NOW()),('王十一', 'wangshiyi_securepwd', 1, 4, NOW() - INTERVAL 6 DAY, NOW()),('李十二', 'lishier_password1', 2, 5, NOW() - INTERVAL 5 DAY, NOW()),('张十三', 'zhangshisan_pwd2', 1, 1, NOW() - INTERVAL 4 DAY, NOW()),('刘十四', 'liushisi_secure3', 2, 2, NOW() - INTERVAL 3 DAY, NOW()),('陈十五', 'chenshiwu_1234', 1, 3, NOW() - INTERVAL 2 DAY, NOW()),('高十六', 'gaoshiliu_pwd5', 2, 4, NOW() - INTERVAL 1 DAY, NOW()),('陆十七', 'lushiqi_securepwd6', 1, null, NOW(), NOW());

二、创建一个新的Springboot项目,并引入对应的起步依赖(Mybatis、mysql驱动、lombok):

三、在默认配置文件application.properties中引入数据库连接信息:

#application.properties文件
spring.application.name=MybatisTest
#驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库连接的url
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_database
#连接数据库的用户名
spring.datasource.username=root
#连接数据库密码
spring.datasource.password=1234

四、创建对应的实体类UserList(定义java类采用驼峰命名法):

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;/*author CHN*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserList {private int id;    // 主键IDprivate String username;    // 用户名private String password;    // 密码private int gender;    // 性别,1表示男,2表示女private int job;    // 部门IDprivate LocalDateTime creatTime;    // 创建时间private LocalDateTime updateTime;    // 修改时间
}

五、创建对应的Mapper接口UserMapper:

import org.apache.ibatis.annotations.Mapper;/*author CHN*/
@Mapper
public interface UserMapper {
}

最终效果: 

删除

        前端发送删除请求以删除数据,为了明确要删除哪一条基础,我们通常选择的唯一的主键来查询并删除。
        明确目标后在UserMapper接口中定义方法:先添加注解删除所需注解@Delete(),括号内补充对应的sql语句并以""包裹,并补充方法(方法体暂时略,下文介绍):

@Mapper
public interface UserMapper {@Delete("delete from user_list where id=17")public void deleteUserById();
}

        但此时我们只能删除id为17的元素,不能满足需求,我们需要使该参数能动态的变化,即根据方法所接收的参数而变化,这里就要用到Mybatis提供的可变参数形式#{},{}内填入对应的参数。为方便测试,我们仍在测试文件中编写代码:

//UserMapper接口————————————————————————————
@Mapper//将其作为bean注入ioc
public interface UserMapper {
//    根据ID删除数据
@Delete("delete from user_list where id=#{targetId}")//可变参数
public void deleteUserById(Integer targetId);
}
//测试类————————————————————————————————————
@SpringBootTest
class MybatisTestApplicationTests {@Autowired//使用ioc中的bean创建实例private UserMapper userMapper;@Testvoid test() {userMapper.deleteUserById(15);}
}

        执行test方法,返回数据库并刷新即可看到id为15的那条数据被删除

        注意deleteUserById方法因为我们无需返回值所以设置为void方法,但实际上它是有返回值的,其返回值代表此次操作操作影响了几条数据。

        如果该接口方法只传入一个普通参数,那#{}中的属性名可以随便写,但不建议知道就好

        此时数据已被成功删除,但Mybatis底层到底执行了什么sql语句,执行结果是什么我们并不知道。在Mybatis框架中,我们可以借助日志来查看这一信息,日志默认是关闭的,想要开启则需在配置文件application.properties添加相关语句:

#配置Mybatis的日志,并将其输出到控制台
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

        之后再执行,可以看到控制台明显多了些语句,其中最重要的语句为:

==>  Preparing: delete from user_list where id=?
==> Parameters: 16(Integer)
<==    Updates: 1 
  • 第一行中的"?"为参数占位符,代表可变参数
  • 第二行代表将Inreger类的参数16传入
  • 第三行代表影响了一条数据

预编译SQL

        这种使用了可变参数的方式为预编译SQL,即在数据库服务器上预编译一条SQL语句,并在之后的操作中多次绑定不同的参数来执行这条语句。这种方法不仅可以提高SQL执行的效率,还能增强安全性,特别是防止SQL注入攻击。

        我们先来介绍如何提高执行效率:
提高效率:
        我们java项目中编写sql语句后,需先连接上数据库并发送语句,数据库接收语句后还要进行语法解析、优化SQL、编译SQL才能执行。为了提高效率,其会将前三步结束后的数据缓存下来,如果下次执行相同语句则可跳过前三步,直接执行。
        之前写的SQL语句preparing部分(Parameters部分为空):

delete from user_list where id=16;
delete from user_list where id=17;
delete from user_list where id=18;

        每次执行语句都需执行一遍前三步,效率较低,使用可变参数后SQL语句preparing部分(Parameters部分依次为16(Integer)、17(Integer)、18(Integer)):

delete from user_list where id=?;-- 16、17、18依次传入替代?

        因为语句相同,所以前三步只需执行一次即可,16、17、18依次传入并执行,与之前的执行结果相同。再来看防止SQL注入攻击:

SQL注入攻击

        SQL注入攻击的原理是利用输入字段或URL参数等,将恶意SQL语句插入到应用程序的查询中。由于应用程序在执行SQL查询时,直接将用户输入的数据拼接到查询字符串中,未经充分验证或转义,攻击者可以通过构造特定的输入,使数据库执行意外的SQL语句。

        我们来看一个例子:登录页面输入账号和密码,正确通过,错误则不通过,对应sql语句preparing部分为(Parameters部分为空):

select count(*) from user_list where username='用户名'and password='密码';

        如果该数据正确则返回1,不正确则返回0,但如果在输入密码时为'or '1'='1呢,此时日志中SQL语句就会变为:

==>  Preparing: select count(*) from user_list where username='用户名'and password=''or '1'='1';
==> Parameters: 
<==    Columns: count(*)
<==        Row: 20
<==      Total: 1

        密码的判断部分永远为真,程序判断失效,这就是一个简单的SQL注入。

        再来看预编译SQL(即使用了可变参数),日志中其sql语句就会变为:

==>  Preparing: select count(*) from user_list where username=? and password=? ;
==> Parameters: 用户名(String),'or '1'='1(String)
<==    Columns: count(*)
<==        Row: 0
<==      Total: 1

        可以看到有两个'?',代表两个可变参数,Parameters部分的参数作为String传入,此时传入的参数就不会影响到程序执行,这就是防止SQL注入攻击。

参数占位符不止#{...}还有${...}

#{...}执行时,会将其替换成?并生成预编译SQL语句,后续参数自动替换。

${...}将该参数直接拼接在SQL语句中,存在一定的SQL注入风险,一般对表名或字段名进行设置时使用。

增加

        先根据表结构编写执行插入操作的sql语句(因为id为自增,所以无需添加):

INSERT INTO user_list (username, password, gender, job, creat_time, update_time)
VALUES ('刘涛', 'liutao_password1', 1, 1, NOW() - INTERVAL 12 DAY, NOW() - INTERVAL 12 DAY),('王芳', 'wangfang_securepwd2', 2, 2, NOW() - INTERVAL 11 DAY, NOW() - INTERVAL 11 DAY);

        再在UserMapper接口中添加对应方法和注解@Insert():

@Insert("INSERT INTO user_list (username, password, gender, job, creat_time, update_time)" +"VALUES ('刘涛', 'liutao_password1', 1, 1, NOW() - INTERVAL 12 DAY, NOW() - INTERVAL 12 DAY)," +"       ('王芳', 'wangfang_securepwd2', 2, 2, NOW() - INTERVAL 11 DAY, NOW() - INTERVAL 11 DAY);")
public void insertUsers();

         与上文相同,这样的参数不可变,需变为可变参数,也可以和上文一样声明参数并传递,但因为参数较多,会导致代码可读性较弱难以维护,因此我们选择将这些参数封装到一个对象中,即封装到之前创建的UserList类对象中:

@Insert("INSERT INTO user_list (username, password, gender, job, creat_time, update_time)" +"VALUES (#{username},#{password},#{gender},#{job},#{creatTime},#{updateTime});" )
public void insertUsers(UserList userList);

        这样方法就已编写好了,我们继续在测试类中模拟前端发起请求:

    @Testvoid test() {//自行添加无id参数的构造函数UserList target = new UserList("王芳", "wangfang_securepwd2", 2, 2, LocalDateTime.now(), LocalDateTime.now());userMapper.insertUsers(target);}
//控制台输出
==>  Preparing: INSERT INTO user_list (username, password, gender, job, creat_time, update_time)VALUES (?,?,?,?,?,?);
==> Parameters: 王芳(String), wangfang_securepwd2(String), 2(Integer), 2(Integer), 2024-11-27T22:08:06.845000400(LocalDateTime), 2024-11-27T22:08:06.845000400(LocalDateTime)
<==    Updates: 1

        可以看到可变参数和参数一一对应并传入,实现了增加数据。

主键返回

        主键返回指的是在数据库表中插入一条新记录后,将该记录的主键值返回。
        我们以表多对多的关系为例,中间表关联着A和B表的id,当我们在A表新建一条数据时,需要得到该数据id的值并返回给中间表,以便建立多对多的关系。

        实现方法:在添加数据的接口方法上添加注解@Options:

//UserMapper
@Mapper
public interface UserMapper {@Options(useGeneratedKeys = true,keyProperty = "id")@Insert("INSERT INTO user_list (username, password, gender, job, creat_time, update_time)" +"VALUES (#{username},#{password},#{gender},#{job},#{creatTime},#{updateTime});" )public void insertUsers(UserList userList);
}
//test类中添加getid()
@Test
void test() {UserList target = new UserList("测试用例", "wangfang_securepwd2", 2, 2, LocalDateTime.now(), LocalDateTime.now());userMapper.insertUsers(target);System.out.println(target.getId());
}
//控制台输出
<==    Updates: 1
Closing non transactional SqlSession 
50

        执行结果显示控制台输出了新插入的数据的id为50。 

修改

        与删除类似:前端发送修改请求以修改数据,为了明确要修改哪一条基础,我们通常选择的唯一的主键来查询并修改。
        先根据表结构编写执行修改操作的sql语句:

update user_list
set username='',password='',gender='',job='',update_time='',username=''
where id = 33;

        再在UserMapper接口中添加对应方法和注解@Update(),与添加字段相同,我们将需要的参数封装到userList对象中:

//UserMapper接口————————————————————————————
@Update("update user_list set username=#{username},password=#{password},gender=#{gender},job=#{job},update_time=#{updateTime} where id = #{id};")
public void updateUsers(UserList userList);
//测试类————————————————————————————————————@Testvoid testUpdate() {UserList target = new UserList(33,"测试用例", "test2", 2, 2, LocalDate.now(), LocalDateTime.now());userMapper.updateUsers(target);}
//控制台输出
==>  Preparing: update user_list set username=?,password=?,gender=?,job=?,update_time=? where id = ?;
==> Parameters: 测试用例(String), test2(String), 2(Integer), 2(Integer), 2024-11-28T01:39:21.615929600(LocalDateTime), 33(Integer)
<==    Updates: 1

        执行testUpdate方法,返回数据库并刷新即可看到id为33的那条数据被修改。

查询      

        同样根据唯一的主键来查询,执行查询操作的sql语句:

select *
from user_list where id=38;

        再在UserMapper接口中添加对应方法和注解@Select(),注意之前的方法返回值都是void,查询方法则应返回对应的类:

//UserMapper接口————————————————————————————@Select("select * from user_list where id=#{id};")public UserList getUserById(Integer id);
//测试类————————————————————————————————————@Testvoid testSelect() {UserList target = userMapper.getUserById(38);System.out.println(target);}
//控制台输出
==>  Preparing: select * from user_list where id=?;
==> Parameters: 38(Integer)
<==    Columns: id, username, password, gender, job, creat_time, update_time
<==        Row: 38, 郑十, zhengshi_pwd, 2, 3, 2024-11-19 22:53:11, 2024-11-26 22:53:11
<==      Total: 1
UserList(id=38, username=郑十, password=zhengshi_pwd, gender=2, job=3, creatTime=null, updateTime=null)

        此时数据虽已被查询出来,但最后两个字段却为空,这是因为有关该字段java类中和数据库中的名字不一样,一个为creatTime,一个为creat_time。
        因为如果实体类属性名和数据库查询返回的字段名一样,Mybayis会自动封装,不一样则不会封装。

        有两种较笨重的解决方法:

  1. 在sql语句中给名称不一样的字段起别名。
  2. 添加注解@Results和@Result。
//方法一:起别名使字段名相同
@Select("select id, username, password, gender, job," +"creat_time creatTime, update_time updateTime from user_list where id=#{id};")
public UserList getUserById(Integer id);
//方法二:添加注解Results
@Results({@Result(column="creat_time",property="creatTime"),@Result(column="update_time",property="updateTime")
})
@Select("select * from user_list where id=#{id};")
public UserList getUserById(Integer id);
//控制台输出
UserList(id=38, username=郑十, password=zhengshi_pwd, gender=2, job=3, creatTime=2024-11-19, updateTime=2024-11-26T22:53:11)

        @Results()注解内部需要一个数组,数组内部则为@Result注解,@Result注解中column为原先的字段名,property为java类中的属性名。

        这两种方法都比较臃肿,我们知道就好。
        接下来介绍第三种方法:开启Mybatis的驼峰命名自动映射开关。

        想要开启也需在配置文件application.properties添加相关语句:

#开启Mybatis的驼峰命名自动映射开关
mybatis.configuration.map-underscore-to-camel-case=true

        这样类似于creat_time的字段就会自动被修改为creatTime。

条件查询

        先来看sql语句:查询userList中名称中包含”十“,gender为1,job为3或5的人,查询结果根据创建时间倒序排序:

select *
from user_list
where username like '%十%'and gender = 1and job in (3, 5)
order by creat_time desc;

        再在UserMapper接口中添加对应方法和注解@Select(),因为可能返回多条数据,所以应定义一集合接收返回数据,但要注意username为%十%,按照之前的方法应为username like '%#{name}%',但#{}不允许出现在''之中1,所以我们应使用${}替换:

//UserMapper接口————————————————————————————@Select("select * from user_list where username like '%${name}%' and gender = #{gender} and job in (#{job1}, #{job2}) order by creat_time desc;")public List<UserList> targetList(String name,int gender,int job1,int job2);
//测试类————————————————————————————————————@Testvoid testSelect() {List<UserList> targetList = userMapper.targetList("十", 1, 3, 5);System.out.println(targetList);}
//控制台输出
==>  Preparing: select * from user_list where username like '%十%' and gender = ? and job in (?, ?) order by creat_time desc;
==> Parameters: 1(Integer), 3(Integer), 5(Integer)
<==    Columns: id, username, password, gender, job, creat_time, update_time
<==        Row: 45, 陆十七, lushiqi_securepwd6, 1, 5, 2024-11-26 22:53:11, 2024-11-26 22:53:11
<==        Row: 43, 陈十五, chenshiwu_1234, 1, 3, 2024-11-24 22:53:11, 2024-11-26 22:53:11
<==      Total: 2
[UserList(id=45, username=陆十七, password=lushiqi_securepwd6, gender=1, job=5, creatTime=2024-11-26, updateTime=2024-11-26T22:53:11), 
UserList(id=43, username=陈十五, password=chenshiwu_1234, gender=1, job=3, creatTime=2024-11-24, updateTime=2024-11-26T22:53:11)]

        但使用了${}又会导致性能低、sql注入等问题, 我们可以通过concat字符拼接函数来解决。

CONCAT

        CONCAT是一个字符串连接函数,它主要用于将两个或多个字符串连接成一个字符串,语法为:CONCAT(string1, string2, ..., stringN)。
        使用该函数就可以将%十%拆分为三部分,这样就可以使用#{}了:

//修改前:@Select("select * from user_list where username like '%${name}%' and gender = #{gender} and job in (#{job1}, #{job2}) order by creat_time desc;")public List<UserList> targetList(String name,int gender,int job1,int job2);
//修改后:@Select("select * from user_list where username like concat('%',#{name},'%') and gender = #{gender} and job in (#{job1}, #{job2}) order by creat_time desc;")public List<UserList> targetList(String name,int gender,int job1,int job2);

参数名说明

        在SpringBoot的2.X版本,只需注意参数名和#{}中的参数名相同即可,但在早期版本,系统会将参数名编译为var1、var2,这时就需要使用注解@Param来标明该字段对应的#{}中的字段:

//老版本:@Delete("delete from user_list where id=#{targetId}")public void deleteUserById(@Param("targetId") Integer Id);
//现版本:@Delete("delete from user_list where id=#{targetId}")public void deleteUserById(Integer Id);

        在上篇文章的引入的第三部分编写sql语句我们提到过:编写语句实现Mybatis有两种方式1、通过注解实现Mybatis,2、通过xml实现Mybatis,第一种已经在上文中讲过了,接下来看第二种方法。

XML映射文件

        xml文件放在对应的resourse文件下,在Mybatis中定义xml映射文件需要遵守规范:

一、XML映射文件的名称与Mapper接口名称一致,且XML映射文件和Mapper接口应放在相同的对应包下(同包同名)。

        右键Resources-New-Directory-输入org/example/mybatistest/Mapper-右键新建文件-New-FIle-UserMapperxml:

        在定义java类时我们通常采用"."来标示不同级的目录,例如package1.package2.package3,但在resources中我们需要以"/"分隔,例如package1/package2/package3,其次就是需要保证两文件同包同名:

二、XML文件的namespace属性需与Mapper接口的全类名保持一致。

        从Mybatis官网复制xml文件,并修改namesapce属性为自己的全类名(在UserMapper接口中右键接口名-Copy Reference即可获取全类名):复制"探究已映射的 SQL 语句"部分代码icon-default.png?t=O83Ahttps://mybatis.net.cn/getting-started.html

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.example.mybatistest.Mapper.UserMapper"><select id="targetList"></select>
</mapper>

三、XML文件中的sql语句中的id需与Mapper接口中的方法名保持一致,并保持返回类型一致。

<!--UserMapper.xml文件:-->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.example.mybatistest.Mapper.UserMapper"><select id="targetList" resultType="org.example.mybatistest.pojo.UserList">select *from user_listwhere username like concat('%', #{name}, '%')and gender = #{gender}and job in (#{job1}, #{job2})order by creat_time desc;</select>
</mapper>
//UserMapper.java文件
@Mapper
public interface UserMapper {public List<UserList> targetList(String name,int gender,int job1,int job2);
}
//测试文件:
@SpringBootTest
class MybatisTestApplicationTests {@Autowiredprivate UserMapper userMapper;@Testvoid testSelect() {List<UserList> targetList = userMapper.targetList("十", 1, 3, 5);System.out.println(targetList);}
}

        因为在UserMapper.java文件下方法名为targetList,因此select id="targetList" ,又因为返回类型需要保持一致,resultType即结果类型,应写全类名,所以resultType="org.example.mybatistest.pojo.UserList">,此时再执行testSelect即可看到返回值相同。

        为什么要遵守这三点规范:我们通过Mybatis操作数据库是通过调用接口中的方法,再执行与该方法对应的sql语句即可完成操作,难点在于怎么找到与方法对应的sql语句。
        之前通过注解操作时执行方法就是执行在方法上方注解中的sql语句,但使用xml文件时方法和sql语句是分开的,因此我们需要根据接口中的方法名找到对应的sql语句。
        执行了以上三点规范执行方法后,Mybatis会自动查找“namespsce”与“该方法所在接口文件的全类名”相同的xml映射文件,并查找id与方法名相同的sql语句并执行,返回值为“resultType”与“该类所在文件的全类名”相同的类。

        但因为方法和sql语句不在同一个地方,一旦有多个方法对应的语句会使我们难以判断相应的方法和sql语句在哪,MyBatisX插件可以帮助我们解决这一问题:

MyBatisX插件

        MyBatisX为IntelliJ IDEA中的一款插件,它旨在简化MyBatis框架的使用,提升开发效率。MyBatisX能够根据数据库表结构自动生成Mapper接口、Mapper XML文件和Java实体类。这减少了手动编写重复代码的工作量,提高了开发效率。

        并且该插件还会在接口类和xml文件左侧加上一一对应的快捷跳转按钮,以便我们查找对应语句在哪:

        对于是使用注解还是xml映射,官方已对其做出说明:

        使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。

        选择何种方式来配置映射,以及认为是否应该要统一映射语句定义的形式,完全取决于你和你的团队。 换句话说,永远不要拘泥于一种方式,你可以很轻松的在基于注解和 XML 的语句映射方式间自由移植和切换。

有关namespace

        在之前版本的 MyBatis 中,命名空间(Namespaces)的作用并不大,是可选的。 但现在,随着命名空间越发重要,你必须指定命名空间。

        命名空间的作用有两个,一个是利用更长的全限定名来将不同的语句隔离开来,同时也实现了你上面见到的接口绑定。就算你觉得暂时用不到接口绑定,你也应该遵循这里的规定,以防哪天你改变了主意。 长远来看,只要将命名空间置于合适的 Java 包命名空间之中,你的代码会变得更加整洁,也有利于你更方便地使用 MyBatis。

        命名解析:为了减少输入量,MyBatis 对所有具有名称的配置元素(包括语句,结果映射,缓存等)使用了如下的命名解析规则。

  • 全限定名(比如 “com.mypackage.MyMapper.selectAllThings)将被直接用于查找及使用。
  • 短名称(比如 “selectAllThings”)如果全局唯一也可以作为一个单独的引用。 如果不唯一,有两个或两个以上的相同名称(比如 “com.foo.selectAllThings” 和 “com.bar.selectAllThings”),那么使用时就会产生“短名称不唯一”的错误,这种情况下就必须使用全限定名。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

火山引擎VeDI在AI+BI领域的演进与实践

随着数字化时代的到来&#xff0c;企业对于数据分析与智能决策的需求日益增强。作为新一代企业级数据智能平台&#xff0c;火山引擎数智平台VeDI基于字节跳动多年的“数据驱动”实践经验&#xff0c;也正逐步在AI&#xff08;人工智能&#xff09;与BI&#xff08;商业智能&…

鼠标前进后退键改双击,键盘映射(AutoHotkey)

初衷&#xff1a; 1.大部分鼠标为不可自定义按键&#xff0c;可以自定义的又很贵。 鼠标左键是双击是很频类很高的操作&#xff0c;鼠标前进/后退按键个人感觉使用频率很低&#xff0c;因此把鼠标前进/后退改为双击还是很合适的。 2.有些短款的键盘没有Home或End键&#xff0c;…

IntelliJ IDEA安装内网穿透实现远程连接家里或公司的MySQL数据库助力开发

文章目录 前言1. 本地连接测试2. Windows安装Cpolar3. 配置Mysql公网地址4. IDEA远程连接Mysql5. 固定连接公网地址6. 固定地址连接测试 前言 本教程主要介绍如何使用Cpolar内网穿透工具实现在IDEA中也可以远程访问家里或者公司的数据库&#xff0c;提高开发效率&#xff01;无…

联想品牌的电脑 Bios 快捷键是什么?如何进入 Bios 设置?

在某些情况下&#xff0c;您可能需要通过U盘来安装操作系统或进行系统修复。对于联想电脑用户来说&#xff0c;了解如何设置U盘作为启动设备是非常有用的技能之一。本文简鹿办公将指导您如何使用联想电脑的 U 盘启动快捷键来实现这一目标。 联想笔记本 对于大多数联想笔记本电…

MCU跨领域融合的风向标是什么?

【哔哥哔特导读】从市场竞争的加剧到技术发展的需求&#xff0c;从智能化趋势到安全性要求的提高&#xff0c;再到市场需求的变化&#xff0c;这些因素共同推动了MCU趋势的发展&#xff0c;那么&#xff0c;当前的发展方向是怎样的&#xff1f; 随着技术的飞速发展和市场需求的…

【Android+多线程】IntentService 知识总结:应用场景 / 使用步骤 / 源码分析

定义 IntentService 是 Android中的一个封装类&#xff0c;继承自四大组件之一的Service 功能 处理异步请求 & 实现多线程 应用场景 线程任务 需 按顺序、在后台执行 最常见的场景&#xff1a;离线下载不符合多个数据同时请求的场景&#xff1a;所有的任务都在同一个T…

AI自动化剪辑工具:可将长视频中精彩部分提取合成短视频

最近&#xff0c;我发现了一款特别适合当下短视频潮流的自动化工具&#xff0c;它能够让我们轻松从长视频中剪辑出精彩片段&#xff0c;并快速生成适合分享的短视频。 这款工具叫 AI Youtube Shorts Generator&#xff0c;是一个开源项目&#xff0c;特别适合那些喜欢制作短视…

Basemap 在地图上显示图例

1.卫星图像绘制 import matplotlib.pyplot as plt from mpl_toolkits.basemap import Basemap # 图像绘制 plt.figure(dpi300) m Basemap(projectioncyl, llcrnrlat11, llcrnrlon105, urcrnrlat35, urcrnrlon135)raw_lat raw_lat[490:1080, 655:1470] raw_lon raw_lon[490:…

GitLab历史演进

GitLab 是一个基于 Git 的 DevOps 平台&#xff0c;它的历史演进反映了开发和运维协作工具的不断发展。GitLab 的目标是为开发团队提供一个集成的工具集&#xff0c;涵盖 源代码管理、CI/CD、项目管理 等功能。GitLab 最初只是一个 Git 仓库管理工具&#xff0c;但随着时间的推…

elasticsearch单节点模式部署

原文地址&#xff1a;elasticsearch单节点模式部署 – 无敌牛 欢迎参观我的个人博客&#xff1a;无敌牛 – 技术/著作/典籍/分享等 第一步&#xff1a;下载 官方下载地址&#xff1a;Download Elasticsearch | Elastic&#xff0c;可以 wget 直接下载。 命令&#xff1a;wg…

26页PDF | 数据中台能力框架及评估体系解读(限免下载)

一、前言 这份报告详细解读了数据中台的发展历程、核心概念、能力框架及成熟度评估体系。它从阿里巴巴的“大中台&#xff0c;小前台”战略出发&#xff0c;探讨了数据中台如何通过整合企业内部的数据资源和能力&#xff0c;加速业务迭代、降低成本&#xff0c;并推动业务增长…

音视频入门基础:MPEG2-TS专题(8)——TS Header中的适配域

注&#xff1a;本文有部分内容引用了维基百科&#xff1a;https://zh.wikipedia.org/wiki/MPEG2-TS 一、引言 当TS Header中的adaptation_field_control属性的值为10或11 时&#xff0c;TS Header包含adaptation field&#xff08;适配域&#xff09;&#xff1a; 根据《T-RE…

挑战用React封装100个组件【001】

项目地址 https://github.com/hismeyy/react-component-100 组件描述 组件适用于需要展示图文信息的场景&#xff0c;比如产品介绍、用户卡片或任何带有标题、描述和可选图片的内容展示 样式展示 代码展示 InfoCard.tsx import ./InfoCard.cssinterface InfoCardProps {ti…

百度智能云千帆部署流程---语音识别和合成

目录 一、前期准备 二、语音合成 三、语音识别 实现整个流程如下图&#xff0c;但是我们的工作量并不是很多&#xff0c;我们可以在官网找到示例代码 一、前期准备 这里我们使用到3个代码 API_KEY.py 填写我们的API xzarm_asr.py 语音识别 xzarm_tts.py 语音合…

33 基于单片机的智能窗帘控制系统

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于51单片机&#xff0c;采用DHT11温湿度传感器检测温湿度&#xff0c;滑动变阻器连接ADC0832数模转换器转换模拟,光敏传感器&#xff0c;采用GP2D12红外传感器&#xff0c;通过LCD1602显示屏显示…

抓包之OSI七层模型以及TCPIP四层模型

写在前面 本文看下OSI七层模型以及TCP/IP四层网络模型&#xff0c;并尝试使用wireshark进行验证。 1&#xff1a;OSI七层网络模型和TCP/IP四层模型 全称&#xff1a;open system interconnection。 需要注意OSI七层模型最终是没有落地的&#xff0c;最终落地的是与之类似的…

华为海思2025届校招笔试面试经验分享

目前如果秋招还没有offer的同学&#xff0c;可以赶紧投递下面这些公司&#xff0c;都在补招。争取大家年前就把后端offer拿下。如果大家在准备秋招补录取过程中有任何问题&#xff0c;都可以私信小编&#xff0c;免费提供帮助。如果还有部分准备备战春招的同学&#xff0c;也可…

05_JavaScript注释与常见输出方式

JavaScript注释与常见输出方式 JavaScript注释 源码中注释是不被引擎所解释的&#xff0c;它的作用是对代码进行解释。lavascript 提供两种注释的写法:一种是单行注释&#xff0c;用//起头:另一种是多行注释&#xff0c;放在/*和*/之间。 //这是单行注释/* 这是 多行 注释 *…

【动手学电机驱动】STM32-FOC(8)MCSDK Profiler 电机参数辨识

STM32-FOC&#xff08;1&#xff09;STM32 电机控制的软件开发环境 STM32-FOC&#xff08;2&#xff09;STM32 导入和创建项目 STM32-FOC&#xff08;3&#xff09;STM32 三路互补 PWM 输出 STM32-FOC&#xff08;4&#xff09;IHM03 电机控制套件介绍 STM32-FOC&#xff08;5&…

Django+Nginx+uwsgi网站Channels+redis+daphne多人在线聊天实现粘贴上传图片

在DjangoNginxuwsgi网站Channelsredisdaphne多人在线的基础上&#xff08;详见DjangoNginxuwsgi网站使用Channelsredisdaphne实现简单的多人在线聊天及消息存储功能-CSDN博客&#xff09;&#xff0c;实现在输入框粘贴或打开本地图片&#xff0c;上传到网站后返回图片路径&…