文章目录
- 一. Mybatis单表操作
- 删除操作
- 查询操作
- #{} 与 ${}的区别
- 更新操作
- 新增操作
- 二. Mybatis多表操作
一. Mybatis单表操作
删除操作
我们接着使用昨天的表和程序,我们来实现通过id删除数据:
我们这样就可以实现将id = 1的数据进行删除了,但是这样有什么问题呢?这样把代码写死了,所以我们需要动态的接收id来进行删除。
这里介绍两种MyBatis获取参数的两种实现:
1.#{} 占位符替换
2.${} 字符串拼接
这会我们先用,后面给大家讲解区别
// 注解方式
@Delete("delete from userinfo where id = #{id}}")
int deleteById(Integer id);
xml方式:
我们来测试一下结果,但是我们并不想真正的进行落盘操作,所以我们在执行方法时开启事务:
但是我们目前并不能看到Mybatis底层到底执行了什么样的sql语句,我们其实可以借助Mybatis的日志看到的,但是默认是关闭的,我们需要手动开启。
在yml配置文件配置以下信息:
# 配置mybatis的日志,指定输出到控制台
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
再次执行test,我们发现不仅打印了sql信息,而且还显示了我们在Test中执行完毕进行Rolled back
查询操作
根据id查询员工信息
<select id="getById" resultType="com.example.demo.entity.User">
select * from userinfo where id = #{id}
</select>
这里大家需要注意一件事:
当我们进行查询操作时,会进行数据库信息到实体对象是数据封装,这里分为两种情况:
1.实体类属性名和数据库查询返回的字段名一致,mybatis会自动进行封装
2.如果实体类属性名和数据库查询返回的字段名不一致,则不能自动封装,会出现null值的情况
因为我们这里是一致的所以没有出现空的情况,我们来将数据库字段名改一下再来试一下。
我们发现当我们实体类属性名和数据库查询返回的字段名不一致,则不能自动封装,那么如何进行封装呢? 有以下三种方案:
1.起别名: 在SQL语句中,对不一样的列名起别名,别名和实体类属性名一样
<select id="getById" resultType="com.example.demo.entity.User">
select id,username,password,photo,create_time createTime,updatetime,state
from userinfo where id = #{id}
</select>
2.开启驼峰命名: 如果字段名与属性名符合驼峰命名规则,mybatis会自动通过驼峰映射规则
# 开启驼峰命名自动映射,从数据库字段名create_time映射到属性名createTime
mybatis:
configuration:
map-underscore-to-camel-case: true
3.返回字典映射:resultMap 字段名称和程序中属性名不同时,可使用resultMap配置映射
<resultMap id="usermap" type="com.example.demo.entity.User">
<id column="id" property="id"></id>
<result column="create_time" property="createTime"></result>
</resultMap>
如果我们要进行条件查询呢?
我们先使用sql写一下我们需要查询的sql:
select * from userinfo where username like '%张%' and updatetime between 2023-05-12 11:09:01 and 2023-05-12 11:09:01 order by desc;
但是我们的#{}是不能出现在’'之内的,因为我们的#{}生成的是占位符? 所以我们这里不能使用#{},这里我们可以使用${}进行字符串拼接
<select id="getList" resultType="com.example.demo.entity.User">
select * from userinfo
where username like '%${username}%'
and updatetime between #{createTime} and #{updateTime}
order by updatetime desc
</select>
@Test
void getList() {
User user = new User();
user.setUsername("张三");
user.setCreateTime(LocalDateTime.of(2023,05,12,11,9,01));
user.setUpdateTime(LocalDateTime.of(2023,05,12,11,9,01));
List<User> list = userMapper.getList(user);
list.stream().forEach(System.out::println);
}
我们发现这里是直接拼接到sql语句中,但是${}性能低、不安全、存在SQL注入问题
我们可以使用concat函数,concat函数是干什么的呢?我先给大家演示一下:
我们可以发现,其实concat就是一个字符串拼接函数
我们就可以将代码改为#{}的方式
参数名说明:
在SpringBoot的2.x版本:
在SpringBoot的1.x版本/单独使用mybatis:
为啥不是参数直接映射呢?
因为在该版本在编译时,并不会保留参数名,因此我们参数名不能对应,因此需要使用@Param
#{} 与 ${}的区别
1.#{paramName} -> 占位符模式
2.${paramName} -> 直接替换
#{}预编译的SQL有以下优势:
性能更高,更安全(防止SQL注入)
SQL注入是通过操作输入的数据来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法
我们来写一个简单的SQL注入的案例:
因为使用${}传递字符串类型参数时,只会传递数值,所以我们需要手动加’ '转换为字符串
这是我们数据库里的两组数据,我们来测试一下登录功能
我们使用正确的账号密码是可以登录的
使用错误的密码是登录不上去的,我们现在使用SQL注入的方式进行登录
为什么呢?我们这里的密码输入的是错误的,为什么登录成功了
看到这里是不是恍然大悟了。
${}拼接SQL。直接将参数拼接在SQL语句中,存在SQL注入问题,我们在开发中尽量避免使用。
更新操作
修改id用户的密码为password
新增操作
我们给userinfo新增一个用户
@Test
void addUser() {
User user = new User();
user.setUsername("zhangsan");
user.setPassword("123456");
int count = userMapper.addUser(user);
System.out.println("新增了"+ count + "行" );
}
我们没有加@Transactional注解,直接进行落盘操作。
如果我们仅仅只是想获取该操作受影响的行数,操作起来也是比较简单的。
如果返回值想要获取id主键值呢?
我们需要在里面设置两个属性值
二. Mybatis多表操作
我们先在mycnblog下在建一张表
然后插入一条数据,我们创建ActicleInfo的实体类:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Article {
private Integer id;
private String title;
private String content;
private LocalDateTime createTime;
private LocalDateTime updateTime;
private Integer uid;
private Integer rcount;
private Integer state;
}
但是我们还需要username属性,但不再Article中,我们创建一个Article的扩展类
我们创建Article的Mapper接口
@Mapper
public interface ArticleMapper {
//查询文章详情
ArticleVO getDetail(Integer id);
}
<select id="getDetail" resultType="com.example.demo.entity.vo.ArticleVO">
select a.*,u.username from articleinfo a left join userinfo u on u.id = a.uid
where a.id = #{id}
</select>
测试类:
@SpringBootTest
class ArticleMapperTest {
@Autowired
private ArticleMapper articleMapper;
@Test
void getDetail() {
ArticleVO articleVO = articleMapper.getDetail(1);
System.out.println(articleVO);
}
}
为什么我们这里打印了username,我们来查看ArticleVO的字节码
我们发现我们@Data注解的toString只会打印自己的属性不会打印父类属性的信息,这里我们自己重写一下toString()方法
再去执行,信息就可以全部打印了