javaEE-mybatis操作数据库

前言 

在MySQL的学习阶段,我们知道了如何使用JDBC去操作,也正是因为学习了JDBC也知道其操作的繁琐,每次的CRUD操作都需要从数据库连接池中去获取数据库连接,然后再编写SQL语句,并绑定对应的参数,接着通过连接执行SQL语句,如果返回值的话还得处理返回值,最后还得释放连接等资源。明明在增删改查阶段只需要修改SQL语句以及对应的参数和处理返回结果即可,但还是存在很多重复性的操作的。因此,Mybatis就由此而诞生了,Mybatis实现了对JDBC的进一步简化与封装,让Java开发者可以更加简单的去操作数据库。

Mybatis快速入门

Mybatis操作数据库的步骤

1、准备工作(创建SpringBoot项目、数据库表的准备、Java实体类的准备)

2、引入Mybatis的相关依赖,配置Mybatis(数据库连接信息)

3、编写SQL语句(使用注解或者xml)

4、测试代码

File -> New -> Project:

接下来创建对应的数据表以及填充数据:

-- 创建表[用户表]
DROP TABLE IF EXISTS userinfo;
CREATE TABLE `userinfo` (`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,`username` VARCHAR ( 127 ) NOT NULL,`password` VARCHAR ( 127 ) NOT NULL,`age` TINYINT ( 4 ) NOT NULL,`gender` TINYINT ( 4 ) DEFAULT '0' COMMENT '1-男 2-女 0-默认',`phone` VARCHAR ( 15 ) DEFAULT NULL,`delete_flag` TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',`create_time` DATETIME DEFAULT now(),`update_time` DATETIME DEFAULT now(),PRIMARY KEY ( `id` ) 
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4; -- 添加用户信息
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'admin', 'admin', 18, 1, '18612340001' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'zhangsan', 'zhangsan', 18, 1, '18612340002' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' );

创建Java中对应的实体类:

@Data
public class UserInfo {private Integer id;private String username;private String password;private Integer age;private Integer gender;private String phone;private Integer deleteFlag;private Date createTime;private Date updateTime;
}

配置数据库连接:

spring:datasource:url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=falseusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

接下来就是编写查询SQL了(先使用看看效果,后面再学习)

@Mapper // 只能使用@Mapper注解
public interface UserInfoMapper { // 在mapper包下(属于数据层)@Select("select * from userinfo")List<UserInfo> selectAll();
}

接着在UserInfoMapper这个类所在的文件中,右键找到Generate,点击 Test 

按照上述步骤,就会生成一个测试类:

@SpringBootTest // 加载spring的运行环境(为了DI)
class UserInfoMapperTest {@Autowiredprivate UserInfoMapper userInfoMapper;@Test// 标记该方法为测试方法,可以直接运行void selectAll() {List<UserInfo> userInfos = userInfoMapper.selectAll();System.out.println(userInfos);}
}

 我们只需要在@Test注解的左侧点击run即可运行该方法(运行结果如下)

从上述步骤来看,从编写SQL语句到测试的步骤非常简单。 所以的一切都是spring帮我们做好了,我们只需要使用即可。

注意:

1、只有查询的SQL中对应的字段才会在对象对应的属性中有值。但细心的小伙伴可能会发现,删除标志、创建时间、更新时间 也同样没有值,这个我们后面再学习。

2、Mybatis也是一个框架,是独立于Spring框架的,Mybatis是对JDBC的封装,即JDBC在Maven项目中可以使用,同样Mybatis也能在Maven项目中使用。但要注意的是上面的依赖是只能在SpringBoot项目中使用,在Maven项目使用的依赖并不是长那样。

既然已经知道了Mybatis框架的优点,接下来就具体学习Mybatis是如何操作数据库的。

Mybatis的基础操作(注解实现接口类)

使用Mybatis操作数据库的话,我们都是创建一个mapper包用来存放具体的操作接口,然后在对应的接口中去编写方法。接着就只需要Mybatis来实现该接口方法,然后我们再将该接口类注册到Spring的容器中,使用容器来实现依赖注入,接着就是调用对应的操作方法即可。 Mybatis 实现 该接口方法的方式有两种:1、使用注解;2、使用xml。我们先来学习简单的注解。

打印日志

在Mybatis中,我们可以使用日志,来查看SQL语句的执行、参数的传递,以及执行的结果。因此我们需要先配置:

# 配置Mybatis日志
mybatis:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

查询

如果我们想要查询数据库中的数据,只需要创建一个接口方法,并在上方加上@Select注解,里面加上查询的语句即可。

需求:查询id=1的用户

@Mapper // 只能使用@Mapper注解
public interface UserInfoMapper { // 在mapper包下(属于数据层)@Select("select * from userinfo")List<UserInfo> selectAll();@Select("select * from userInfo where id=1")UserInfo selectById();}

测试类 

@SpringBootTest // 加载spring的运行环境(为了DI)
class UserInfoMapperTest {@Autowiredprivate UserInfoMapper userInfoMapper;@Test// 标记该方法为测试方法,可以直接运行void selectAll() {List<UserInfo> userInfos = userInfoMapper.selectAll();System.out.println(userInfos);}@Testvoid selectById() {UserInfo Uer1=userInfoMapper.selectById();}
}

结果

虽然上述方式能够查询id = 1的数据,但是这种方法是写死的,即通过手动指定来查询的数据,但在实际开发中,往往是需要动态的数值。这里就需要用到 "#{参数名}" 的方式了。

  @Select("select * from userInfo where id=#{id}")UserInfo selectById2(Integer id);

测试类 

  void selectById2() {UserInfo Uer2=userInfoMapper.selectById2(2);System.out.println(Uer2);}

结果

注意:

1、如果接口方法里面的形参只有一个时,#{参数名},这里的参数名可以是任意值。

2、如果想改名的话,需要通过@Param注解将修改后的参数名写入其中。

我们在上⾯查询时发现, 有⼏个字段是没有赋值的, 只有Java对象属性和数据库字段⼀模⼀样时, 才会进行赋值

解决方法:

1. 起别名
2. 结果映射
3. 开启驼峰命名
起别名
在SQL语句中,给列名起别名,保持别名和实体类属性名⼀样
@Select("select id, username, `password`, age, gender, phone, delete_flag as 
deleteFlag, " +"create_time as createTime, update_time as updateTime from user_info")
public List<UserInfo> queryAllUser();
结果映射
@Select("select id, username, `password`, age, gender, phone, delete_flag, 
create_time, update_time from user_info")
@Results({@Result(column = "delete_flag",property = "deleteFlag"),@Result(column = "create_time",property = "createTime"),@Result(column = "update_time",property = "updateTime")
})
List<UserInfo> queryAllUser();
如果其他SQL, 也希望可以复⽤这个映射关系, 可以给这个Results定义⼀个名称
使⽤ id 属性给该 Results 定义别名, 使⽤ @ResultMap 注解来复⽤其他定义的 ResultMap
@Select("select id, username, `password`, age, gender, phone, delete_flag, 
create_time, update_time from user_info")
@Results(id = "resultMap",value = {@Result(column = "delete_flag",property = "deleteFlag"),@Result(column = "create_time",property = "createTime"),@Result(column = "update_time",property = "updateTime")
})
List<UserInfo> queryAllUser();
@Select("select id, username, `password`, age, gender, phone, delete_flag, 
create_time, update_time " +"from user_info where id= #{userid} ")
@ResultMap(value = "resultMap")
UserInfo queryById(@Param("userid") Integer id);

 开启驼峰命名(推荐)

通常数据库列使⽤蛇形命名法进⾏命名(下划线分割各个单词), ⽽ Java 属性⼀般遵循驼峰命名法约定.
为了在这两种命名⽅式之间启⽤⾃动映射,需要将 mapUnderscoreToCamelCase 设置为 true
mybatis:configuration:map-underscore-to-camel-case: true #配置驼峰⾃动转换
驼峰命名规则: abc_xyz => abcXyz
表中字段名:abc_xyz
类中属性名:abcXyz

 增加

增加数据,是通过@Insert注解实现的,只需要在里面加上对应的SQL语句即可。 

上述这种方式是比较繁琐的,我们可以使用传输对象的方式:

Insert语句默认返回的是受影响的行数,但在有些情景下,数据插入之后,需要获取的新插入的数据的id,提供给后续的操作。例如,订单系统的中的下完订单之后,后续的物流系统、库存系统等都需要拿到订单的id来进行操作。如果想要拿到自增的id,需要在接口方法上添加Options注解。

获取自动生成的主键值:通过Options注解来获取,其属性useGeneratedKeys=true表示使用数据库自增主键,keyColumn用于指定数据库table中的主键,keyProperty用于指定传入对象的属性(绑定到对象的哪个属性)。如果我们已经在数据库表中指定了主键,并且数据库中主键的名称和Java属性的名称是相同的,那么keyColumn属性可以省略。

   @Options(useGeneratedKeys = true, keyProperty = "id")@Insert("insert into userinfo (username, password, age, gender) values (#{username},#{password},#{age},#{gender})")Integer insert(UserInfo userInfo);

测试代码

 @Testvoid insert() {UserInfo userInfo = new UserInfo();userInfo.setUsername("zhaoliu");userInfo.setPassword("zhaoliu");userInfo.setAge(18);userInfo.setGender(1);Integer result = userInfoMapper.insert(userInfo);System.out.println("result: "+result + ",id:"+userInfo.getId());}

 删除

  @Delete("delete from userinfo where id = #{id}")Integer delete(Integer id);

更新

更新数据,是通过@Update注解实现的,只需要在里面加上对应的SQL语句即可。  

 @Update("update userInfo set username = #{username} where id = #{id}")Integer update(UserInfo userInfo);
 void update() {UserInfo userInfo=new UserInfo();userInfo.setUsername("lisi");userInfoMapper.selectById2(2);Integer result=userInfoMapper.update(userInfo);System.out.println(result);}

XML实现接口类 

快速上手 

配置数据源和导入依赖与前面是一样的。下面是不一样的配置:

1、配置 Mybatis 的 xml文件路径

mybatis: # classpath 表示resources目录下,整体表示的是 resources/mapper/所有xml文件# * 代表通配符,表示只要是 .xml 文件即可mapper-locations: classpath:mapper/*.xml

第二步同样还是创建接口类,第三步不再是通过注解来实现SQL语句了,而是通过在配置的XML文件目录下创建XML文件,在文件中编写对应的实现SQL语句。

在新创建的XML文件中,添加下面的标签: 

<?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">
<!-- namespace 表示的该xml文件实现的接口类的全限定名称 -->
<mapper namespace=""></mapper>

 这里的namespace的值就是UserInfoMapperXML文件的全限定类名。

例如:com.example.demo.UserInfoMapperXML

这里再介绍一个插件: mybatisx,

安装该插件之后,会有两个好处:

1、知道XML文件中的mapper标签的namespace值是否绑定正确。如果绑定正确的话,对应的文件位置就会出现小鸟图标,点击该图标可以在绑定的文件之中任意跳转。 

 2、插件会自动校验对应的接口类的接口方法是否生成了对应的实现标签。如果生成对应的实现标签的话,就会在对应的标签和方法之间生成小鸟图标,同样可以实现自动跳转,反之,如果没有生成的话,就会在对应的接口类所在的方法上爆红。

接下来,就可以在标签中编写对应的SQL语句了。

  <select id="select" resultType="com.example.demo.UserInfo">select * from userinfo;</select>

 最后就是测试对应的SQL语句了,同样在接口类中,鼠标右键 -> Generate -> Test:

查询 

根据id查询:

System.out.println(userInfoMapperXML.select());
<select id="selectbyid" resultType="com.example.demo.UserInfo">select * from userinfo where id = #{id};</select>
 @Testvoid selectbyid() {System.out.println(userInfoMapperXML.selectbyid(3));}

根据 username 和 password 查询:

// 根据username和password查询
UserInfo selectByUsernameAndPassword(String username, String password);@Test
void selectByUsernameAndPassword() {System.out.println(userInfoMapperXML.selectByUsernameAndPassword("admin", "admin_123456"));
}

根据 对象 来查询:

// 根据对象来查询
UserInfo selectByUserInfo(UserInfo userInfo);<select id="selectByUserInfo" resultType="com.springboot.mybatisdemo2.pojo.UserInfo"><!-- 注意,这里#{}内部的是实体类的属性名,其余的都是数据库的字段 -->select * from user_info where username = #{username} and password = #{password}
</select>@Test
void selectByUserInfo() {UserInfo userInfo = new UserInfo();userInfo.setUsername("admin");userInfo.setPassword("admin_123456");System.out.println(userInfoMapperXML.selectByUserInfo(userInfo));
}

由于之前在配置文件中,设置过了驼峰自动转换,因此这里并不会出现对象属性为null的情况。现在我们来看在XML文件中,使用起别名和result标签如何解决。

<!-- 起别名 -->
<select id="selectAll2" resultType="com.springboot.mybatisdemo2.pojo.UserInfo">select id, username, password, age, gender, phone,delete_flag as deleteFlag,create_time as createTime,update_time as updateTime  from user_info
</select><!-- id是给别的标签使用所引用的  type表示映射到哪个实体类上 -->
<resultMap id="resultMap" type="com.springboot.mybatisdemo2.pojo.UserInfo"><!-- 规范的写法是要将数据表中所有字段和实体类的属性相对应的,即使有些字段和属性是对应上的,也最好要加上,因为是规范id 表示主键,result表示普通字段--><id column="id" property="id"/><result column="username" property="username"/><result column="password" property="password"/><result column="age" property="age"/><result column="gender" property="gender"/><result column="phone" property="phone"/><result column="delete_flag" property="deleteFlag"/><result column="create_time" property="createTime"/><result column="update_time" property="updateTime"/>
</resultMap><!-- resultMap表示要引用的映射关系 -->
<select id="selectAll3" resultType="com.springboot.mybatisdemo2.pojo.UserInfo" resultMap="resultMap">select * from user_info
</select>

增加

新增数据,传递参数:

// 新增数据:传递参数
Integer insertByUsernameAndPassword(String username, String password);<insert id="insertByUsernameAndPassword">insert into user_info (username, password) values (#{username}, #{password})
</insert>@Test
void insertByUsernameAndPassword() {System.out.println(userInfoMapperXML.insertByUsernameAndPassword("444", "444"));
}

新增数据,传递对象:

// 新增数据:传递对象
Integer insertByUserInfo(UserInfo userInfo);<insert id="insertByUserInfo">insert into user_info (username, password) values (#{username}, #{password})
</insert>@Test
void insertByUserInfo() {UserInfo userInfo = new UserInfo();userInfo.setUsername("444");userInfo.setPassword("444");System.out.println(userInfoMapperXML.insertByUserInfo(userInfo));
}

新增数据,获取自增id:

// 新增数据:获取自增的id
Integer insertGetGeneratedKey(UserInfo userInfo);<insert id="insertGetGeneratedKey" useGeneratedKeys="true" keyProperty="id">insert into user_info (username, password) values (#{username}, #{password})
</insert>@Test
void insertGetGeneratedKey() {UserInfo userInfo = new UserInfo();userInfo.setUsername("444");userInfo.setPassword("444");System.out.println("受影响的行数: " + userInfoMapperXML.insertGetGeneratedKey(userInfo));System.out.println("获取自增的id: " + userInfo.getId());
}

删除

删除数据,传递id:

// 删除数据:传递参数
Integer deleteById(Integer id);<delete id="deleteById">delete from user_info where id = #{id}
</delete>@Test
void deleteById() {System.out.println(userInfoMapperXML.deleteById(40));
}

删除数据,传递对象: 

// 删除数据:传递对象
Integer deleteByUserInfo(UserInfo userInfo);<delete id="deleteByUserInfo">delete from user_info where username = #{username} and password = #{password}
</delete>@Test
void deleteByUserInfo() {UserInfo userInfo = new UserInfo();userInfo.setUsername("admin");userInfo.setPassword("admin");System.out.println(userInfoMapperXML.deleteByUserInfo(userInfo));
}

更新

更新数据,传递参数 

// 更新数据:传递参数
Integer updateByUsernameAndPassword(Long id, String username, String password);<update id="updateByUsernameAndPassword">update user_info set username = #{username}, password = #{password} where id = #{id}
</update>@Test
void updateByUsernameAndPassword() {System.out.println(userInfoMapperXML.updateByUsernameAndPassword(33 L, "admin", "admin"));
}

多表查询

由于实际开发中,多表查询的使用频率不是很高,因此这里不再演示了。 感兴趣可以看看别的博主文章

#{} 和 ${} 的区别

MyBatis 参数赋值有两种⽅式, 咱们前⾯使⽤了 #{} 进⾏赋值, 接下来我们看下⼆者的区别
1 @Select("select username, `password`, age, gender, phone from user_info where 
id= #{id} ")
2 UserInfo queryById(Integer id);

1 select username, `password`, age, gender, phone from user_info where id= ?
我们输⼊的参数并没有在后⾯拼接,id的值是使⽤ ? 进⾏占位. 这种SQL 我们称之为"预编译SQL"
我们把 #{} 改成 ${} 再观察打印的⽇志
@Select("select username, `password`, age, gender, phone from user_info where id= ${id} ") 2 UserInfo queryById(Integer id); 

可以看到, 这次的参数是直接拼接在SQL语句中了
接下来我们再看String类型的参数
@Select("select username, `password`, age, gender, phone from user_info where 
username= #{name} ")
UserInfo queryByName(String name);

我们把 #{} 改成 ${} 再观察打印的⽇志
@Select("select username, `password`, age, gender, phone from user_info where
username= ${name} ") 
UserInfo queryByName(String name);

可以看到, 这次的参数依然是直接拼接在SQL语句中了, 但是字符串作为参数时, 需要添加引号 '' , 使
${} 不会拼接引号 '' , 导致程序报错.
修改代码如下:
@Select("select username, `password`, age, gender, phone from user_info where 
username= '${name}' ")
UserInfo queryByName(String name);
再次运⾏, 结果正常返回
从上⾯两个例⼦可以看出:
#{} 使⽤的是预编译SQL, 通过 ? 占位的⽅式, 提前对SQL进⾏编译, 然后把参数填充到SQL语句
中. #{} 会根据参数类型, ⾃动拼接引号 '' .
${} 会直接进⾏字符替换, ⼀起对SQL进⾏编译. 如果参数为字符串, 需要加上引号 '' .
参数为数字类型时, 也可以加上, 查询结果不变, 但是可能会导致索引失效, 性能下降.
#{} 和 ${} 的区别就是预编译SQL和即时SQL 的区别
简单回顾:
当客⼾发送⼀条SQL语句给服务器后, ⼤致流程如下:
1. 解析语法和语义, 校验SQL语句是否正确
2. 优化SQL语句, 制定执⾏计划
3. 执⾏并返回结果
⼀条 SQL如果⾛上述流程处理, 我们称之为 Immediate Statements(即时 SQL)
1. 性能更⾼
绝⼤多数情况下, 某⼀条 SQL 语句可能会被反复调⽤执⾏, 或者每次执⾏的时候只有个别的值不同
如 select 的 where ⼦句值不同, update 的 set ⼦句值不同, insert 的 values 值不同). 如果每次都需要 经过上⾯的语法解析, SQL优化、SQL编译等,则效率就明显不⾏了.
预编译SQL,编译⼀次之后会将编译后的SQL语句缓存起来,后⾯再次执⾏这条语句时,不会再次编译
(只是输⼊的参数不同), 省去了解析优化等过程, 以此来提⾼效率
2. 更安全(防⽌SQL注⼊)
SQL注⼊:是通过操作输⼊的数据来修改事先定义好的SQL语句,以达到执⾏代码对服务器进⾏攻击的 ⽅法。
由于没有对⽤⼾输⼊进⾏充分检查,⽽SQL⼜是拼接⽽成,在⽤⼾输⼊参数时,在参数中添加⼀些
SQL关键字,达到改变SQL运⾏结果的⽬的,也可以完成恶意攻击。
@Select("select username, `password`, age, gender, phone from user_info where 
username= '${name}' ")
List<UserInfo> queryByName(String name);
sql 注⼊代码: ' or 1='1
正常访问情况:
@Test
void queryByName() {List<UserInfo> userInfos = userInfoMapper.queryByName("admin");System.out.println(userInfos);
} 
结果运⾏正常

SQL注⼊场景:

 

1 @Test
2 void queryByName() {
3 List<UserInfo> userInfos = userInfoMapper.queryByName("' or 1='1");
4 System.out.println(userInfos);
5 }
结果依然被正确查询出来了, 其中参数 or被当做了SQL语句的⼀部分
可以看出来, 查询的数据并不是⾃⼰想要的数据. 所以⽤于查询的字段,尽量使⽤ #{} 预查询的⽅式
SQL注⼊是⼀种⾮常常⻅的数据库攻击⼿段, SQL注⼊漏洞也是⽹络世界中最普遍的漏洞之⼀.
如果发⽣在⽤⼾登录的场景中, 密码输⼊为 ' or 1='1 , 就可能完成登录
排序功能
从上⾯的例⼦中, 可以得出结论: ${} 会有SQL注⼊的⻛险, 所以我们尽量使⽤#{}完成查询
既然如此, 是不是 ${} 就没有存在的必要性了呢?
当然不是
接下来我们看下${}的使⽤场景
Mapper实现
@Select("select id, username, age, gender, phone, delete_flag, create_time, 
update_time " +"from user_info order by id ${sort} ")
List<UserInfo> queryAllUserBySort(String sort);
使⽤ ${sort} 可以实现排序查询, ⽽使⽤ #{sort} 就不能实现排序查询了.
注意: 此处 sort 参数为String类型, 但是SQL语句中, 排序规则是不需要加引号 '' 的, 所以此时的 ${sort} 也不加引号
我们把 ${} 改成 #{}
可以发现, 当使⽤ #{sort} 查询时, asc 前后⾃动给加了引号, 导致 sql 错误
#{} 会根据参数类型判断是否拼接引号 ''
如果参数类型为String, 就会加上 引号.
除此之外, 还有表名作为参数时, 也只能使⽤ ${}
Like查询
like 使⽤ #{} 报错
@Select("select id, username, age, gender, phone, delete_flag, create_time, 
update_time " +"from user_info where username like '%#{key}%' ")
List<UserInfo> queryAllUserByLike(String key);
把 #{} 改成 ${} 可以正确查出来, 但是${}存在SQL注⼊的问题, 所以不能直接使⽤ ${}.
解决办法: 使⽤ mysql 的内置函数 concat() 来处理,实现代码如下:
@Select("select id, username, age, gender, phone, delete_flag, create_time, 
update_time " +"from user_info where username like concat('%',#{key},'%')")
List<UserInfo> queryAllUserByLike(String key);

数据库连接池 

我们前面在学习JDBC时,每次增删改查操作都需要去手动创建连接,当SQL语句执行完毕之后,就需要去手动释放连接,整个过程是比较消耗资源的,特别是频繁创建与销毁连接时。前面学习多线程时,也是需要手动创建新线程,后来学习了如何使用线程池之后,就可以直接向线程池中拿线程,使用完之后,继续放到线程池中,这样的做法就是避免了频繁创建与销毁带来的时间开销。同样,数据库连接也可以存放到数据库连接池中,SpringBoot项目也是封装了数据库连接池:

这里使用到的数据库连接池是 Hikari,如果我们想要使用别的数据库连接池的话,也是可以在pom文件中导入依赖的。
<!-- springboot3.x版本 -->
<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-3-starter</artifactId><version>1.2.21</version>
</dependency><!-- springboot2.x版本 -->
<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.17</version>
</dependency>

再次启动测试用例,观察控制台的输出结果:

好啦!本期 初始 JavaEE篇 —— Mybatis操作数据库(上)的学习之旅 就到此结束啦!我们下一期再一起学习吧!

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

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

相关文章

移动端测试——如何解决iOS端无法打开弹窗式网页(Webkit)

目录 一、什么是webkit&#xff1f; 1. 核心定义 2. iOS 的特殊限制 3. 弹窗拦截的逻辑 二、为什么 iOS 必须用 WebKit&#xff1f; 1. 苹果的官方理由 2. 实际后果 3.然而…… 三、如何解决iOS端无法打开弹窗式网页&#xff1f; 1.用户 1.1 safari浏览器 1.2 夸克…

【github】从本地更新仓库里的文件笔记

1. 打开GitHub官网&#xff0c;并登录到您的账户。 2. 在页面右上角的搜索栏中&#xff0c;输入您要更新的仓库名称&#xff0c;并选择相应的仓库进入。 3. 在仓库页面中&#xff0c;找到并点击红色的“Code”按钮&#xff0c;然后复制仓库的HTTPS或者SSH链接。 4. 右键包含…

Excel基础:数据编辑

Excel是Windows下最常用的数据处理工具&#xff0c;本文详细介绍Excel的数据编辑功能&#xff0c;熟练掌握编辑技巧能可以极大提升工作效率&#xff0c;文章最后附加了一张总结思维导图&#xff0c;方便大家查找和记忆。 文章目录 一、数据输入1.1 覆盖输入1.2 追加输入1.3 任…

JavaScript中Object()的解析与应用

在JavaScript中&#xff0c;Object() 是一个基础构造函数&#xff0c;用于创建对象或转换值为对象类型。它既是语言的核心组成部分&#xff0c;也提供了一系列静态方法用于对象操作。以下是详细解析和应用示例&#xff1a; 一、Object() 的基本行为 作为构造函数&#xff08;…

stream使用案例

1.1 查找所有的偶数并求和 public static void p1() { List<Integer> numbers Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); int sum numbers.stream() .filter(num -> num % 2 0) .mapToInt(Integer::intValue) .sum() ; System.err.printf…

力扣 刷题(第七十一天)

灵感来源 - 保持更新&#xff0c;努力学习 - python脚本学习 4的幂 解题思路 位运算条件&#xff1a;4 的幂的二进制表示中只有一个 1&#xff0c;且位于奇数位&#xff08;如 4 100&#xff0c;4 10000&#xff09;。模运算条件&#xff1a;4 的幂减 1 后能被 3 整除&…

深度学习使用Pytorch训练模型步骤

训练模型是机器学习和深度学习中的核心过程&#xff0c;旨在通过大量数据学习模型参数&#xff0c;以便模型能够对新的、未见过的数据做出准确的预测。 训练模型通常包括以下几个步骤&#xff1a; 1.数据准备&#xff1a; 收集和处理数据&#xff0c;包括清洗、标准化和归一化…

Unity_导航操作(鼠标控制人物移动)_运动动画

文章目录 前言一、Navigation 智能导航地图烘焙1.创建Plan和NavMesh Surface2.智能导航地图烘焙 二、MouseManager 鼠标控制人物移动1.给场景添加人物&#xff0c;并给人物添加导航组件2.编写脚本管理鼠标控制3.给人物编写脚本&#xff0c;订阅事件&#xff08;添加方法给Mouse…

6. 接口分布式测试pytest-xdist

pytest-xdist实战指南&#xff1a;解锁分布式测试的高效之道 随着测试规模扩大&#xff0c;执行时间成为瓶颈。本文将带你深入掌握pytest-xdist插件&#xff0c;利用分布式测试将执行速度提升300%。 一、核心命令解析 加速安装&#xff08;国内镜像&#xff09; pip install …

预训练语言模型

预训练语言模型 1.1Encoder-only PLM ​ Transformer结构主要由Encoder、Decoder组成&#xff0c;根据特点引入了ELMo的预训练思路。 ELMo&#xff08;Embeddings from Language Models&#xff09;是一种深度上下文化词表示方法&#xff0c; 该模型由一个**前向语言模型&…

Altera PCI IP target设计分享

最近调试也有关于使用Altera 家的PCI IP&#xff0c;然后分享一下代码&#xff1a; 主要实现&#xff1a;主控作为主设备&#xff0c;FPGA作为从设备&#xff0c;主控对FPGA IO读写的功能 后续会分享FPGA作为主设备&#xff0c; 从 FPGA通过 memory写到主控内存&#xff0c;会…

基于机器学习的智能文本分类技术研究与应用

在当今数字化时代&#xff0c;文本数据的爆炸式增长给信息管理和知识发现带来了巨大的挑战。从新闻文章、社交媒体帖子到企业文档和学术论文&#xff0c;海量的文本数据需要高效地分类和管理&#xff0c;以便用户能够快速找到所需信息。传统的文本分类方法主要依赖于人工规则和…

前端项目3-01:登录页面

一、效果图 二、全部代码 <!DOCTYPE html> <html><head><meta charset"utf-8"><title>码农魔盒</title><style>.bg{position: fixed;top: 0;left:0;object-fit: cover;width: 100vw;height: 100vh;}.box{width: 950px;he…

Nexus CLI:简化你的分布式计算贡献之旅

探索分布式证明网络的力量&#xff1a;Nexus CLI 项目深入解析 在今天的数字时代&#xff0c;分布式计算和去中心化技术正成为互联网发展的前沿。Nexus CLI 是一个为 Nexus 网络提供证明的高性能命令行界面&#xff0c;它不仅在概念上先进&#xff0c;更是在具体实现中为开发者…

IBW 2025: CertiK首席商务官出席,探讨AI与Web3融合带来的安全挑战

6月26日至27日&#xff0c;全球最大的Web3安全公司CertiK亮相伊斯坦布尔区块链周&#xff08;IBW 2025&#xff09;&#xff0c;首席商务官Jason Jiang出席两场圆桌论坛&#xff0c;分享了CertiK在AI与Web3融合领域的前沿观察与安全见解。他与普华永道土耳其网络安全服务主管Nu…

Vivado 五种仿真类型的区别

Vivado 五种仿真类型的区别 我们还是用“建房子”的例子来类比。您已经有了“建筑蓝图”&#xff08;HLS 生成的 RTL 代码&#xff09;&#xff0c;现在要把它建成真正的房子&#xff08;FPGA 电路&#xff09;。这五种仿真就是在这个过程中不同阶段的“质量检查”。 1. 行为…

小程序快速获取url link方法,短信里面快速打开链接

获取小程序链接方法 uni.request({url:https://api.weixin.qq.com/cgi-bin/token?grant_typeclient_credential&appidwxxxxxxxxxxxx&secret111111111111111111111111111111111,method:GET,success(res) {console.log(res.data)let d {"path": "/xxx/…

Spring 框架(1-4)

第一章&#xff1a;Spring 框架概述 1.1 Spring 框架的定义与背景 Spring 是一个开源的轻量级 Java 开发框架&#xff0c;于 2003 年由 Rod Johnson 创立&#xff0c;旨在解决企业级应用开发的复杂性。其核心设计思想是面向接口编程和松耦合架构&#xff0c;通过分层设计&…

RabitQ 量化:既省内存又提性能

突破高维向量内存瓶颈:Mlivus Cloud RaBitQ量化技术的工程实践与调优指南 作为大禹智库高级研究员,拥有三十余年向量数据库与AI系统架构经验的我发现,在当今多模态AI落地的核心场景中,高维向量引发的内存资源消耗问题已成为制约系统规模化部署的“卡脖子”因素。特别是在大…

创客匠人:创始人 IP 打造的得力助手

在当今竞争激烈的商业环境中&#xff0c;创始人 IP 的打造对于企业的发展愈发重要。一个鲜明且具有影响力的创始人 IP&#xff0c;能够为企业带来独特的竞争优势&#xff0c;提升品牌知名度与美誉度。创客匠人在创始人 IP 打造过程中扮演着不可或缺的角色&#xff0c;为创始人提…