准备工作
○ 创建module(Maven的普通Java模块):mybatis-002-crud
○ pom.xml
■ 打包方式jar
■ 依赖:
● mybatis依赖
● mysql驱动依赖
● junit依赖
● logback依赖
○ mybatis-config.xml放在类的根路径下
○ CarMapper.xml放在类的根路径下(一张表对应一个Mapper)
○ logback.xml放在类的根路径下
○ 提供com.sunsplanter.mybatis.utils.SqlSessionUtil工具类
○ 创建测试用例:com.sunsplanter.mybatis.CarMapperTest
MyBatis 中,既可以在 Mapper XML 文件中编写 SQL,也可以在 Mapper 接口中通过注解编写 SQL。
- 对于复杂, 频繁变更, 涉及动态SQL的 SQL 语句,使用 XML 文件可能更合适;
- 对于简单的 SQL 语句,使用注解可能更方便
- 此外, 调试或优化 SQL 语句时,拥有一个独立的 XML 文件可以让这个过程更加直观和集中。
1.在mapper.xml中编写SQL完成RUD
1.1.insert(Create)
1.1.1 使用map传参完成insert
在第一个mybatis程序中的CarMapper.xml文件中,Sql语句是写死的:
<insert id="insertCar">
//插入的东西全部写死了,但实际使用中,我们不可能预知用户需要insert的数据
//一定是通过某种方法留空,然后前端的form表单传来数据,再将数据与sql语句结合提交
insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)
values(null,'1003','toyota',30.00,'2000-10-11','燃油车')
</insert>
在JDBC中,我们是这样留空的:
// JDBC中使用 ? 作为占位符。
String sql = "insert into t_car(car_num,brand,guide_price,produce_time,car_type) values(?,?,?,?,?)";
在MyBatis中,我们这样做:
在Java程序中,将数据放到Map集合中
在sql语句中使用 #{map集合的key} 来 完成传值,#{} 等同于JDBC中的 ? ,#{}就是占位符
package com.sunsplanter.mybatis;
import com.sunsplanter.mybatis.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
public class CarMapperTest {
@Test
public void testInsertCar() {
//前端传来数据了,用map将这些数据封装起来
Map<String, Object> map = new HashMap<>();
map.put("k1", "103");
map.put("k2", "奔驰E300L");
map.put("k3", 50.3);
map.put("k4", "2020-10-01");
map.put("k5", "燃油车");
//调用封装好的方法获取SqlSession对象
SqlSession sqlSession = SqlSessionUtil.openSession();
// 执行SQL语句,第一个参数是sql段的id,第二参数是要传入的对象(使用map集合给sql语句传递数据)
int count = sqlSession.insert("insertCar", map);
System.out.println("插入了几条记录:" + count);
}
}
<?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先随便写-->
<mapper namespace="TBD
">
<insert id="insertCar">
<!--t_car后接的参数指明要插入表中的哪个单元,而#{} 的里面必须填写map集合的key,以正确获取map的value-->
insert into t_car(car_num,brand,guide_price,produce_time,car_type) values(#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
</insert>
</mapper>
1.1.2使用POJO传参完成insert
如果采用POJO传参,#{} 里写的是get方法的方法名去掉get之后将剩下的单词首字母变小写(例如:getAge对应的是#{age},getUserName对应的是#{userName}),如果这样的get方法不存在会报错。
● 第一步:新建一个pojo包,包下定义一个pojo类Car,提供相关属性。
package com.sunsplanter.mybatis.pojo;
public class Car {
//long还是Long?若使用包装类,其提供了更多的方法可供调用,并且支持值为null的情况。
private Long id;
private String carNum;
private String brand;
private Double guidePrice;
private String produceTime;
private String carType;
@Override
public String toString() {
return "Car{" +
"id=" + id +
", carNum='" + carNum + '\'' +
", brand='" + brand + '\'' +
", guidePrice=" + guidePrice +
", produceTime='" + produceTime + '\'' +
", carType='" + carType + '\'' +
'}';
}
public Car() {
}
public Car(Long id, String carNum, String brand, Double guidePrice, String produceTime, String carType) {
this.id = id;
this.carNum = carNum;
this.brand = brand;
this.guidePrice = guidePrice;
this.produceTime = produceTime;
this.carType = carType;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getCarNum() {
return carNum;
}
public void setCarNum(String carNum) {
this.carNum = carNum;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public Double getGuidePrice() {
return guidePrice;
}
public void setGuidePrice(Double guidePrice) {
this.guidePrice = guidePrice;
}
public String getProduceTime() {
return produceTime;
}
public void setProduceTime(String produceTime) {
this.produceTime = produceTime;
}
public String getCarType() {
return carType;
}
public void setCarType(String carType) {
this.carType = carType;
}
}
@Test
public void testInsertCarByPOJO(){
// 创建POJO,封装数据
Car car = new Car();
//前端传来数据了,用set方法将这些数据传入POJO属性
car.setCarNum("103");
car.setBrand("奔驰C200");
car.setGuidePrice(33.23);
car.setProduceTime("2020-10-11");
car.setCarType("燃油车");
//调用封装好的方法获取SqlSession对象
SqlSession sqlSession = SqlSessionUtil.openSession();
// 执行SQL语句。第一个参数是sql段的id,第二参数是要传入的对象(使用POJO对象给sql语句传递数据)
int count = sqlSession.insert("insertCarByPOJO", car);
System.out.println("插入了几条记录" + count);
}
<insert id="insertCarByPOJO">
<!--#{} 里写的是POJO的属性名-->
insert into t_car(car_num,brand,guide_price,produce_time,car_type) values(#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
</insert>
1.2.Delete
需求:根据car_num进行删除。
@Test
public void testDeleteByCarNum(){
//调用封装好的方法获取SqlSession对象
SqlSession sqlSession = SqlSessionUtil.openSession();
// 执行SQL语句。第一个参数是sql段的id,第二个参数是要删除的“car_num”
int count = sqlSession.delete("deleteByCarNum", "102");
System.out.println("删除了几条记录:" + count);
}
<delete id="deleteByCarNum">
<!--当占位符只有一个的时候,${} 里面的内容可以随便写。-->
delete from t_car where car_num = #{SuiBianXie}
</delete>
1.3.1 用POJO传参Update
需求:修改id=34的Car信息,car_num为102,brand为比亚迪汉,guide_price为30.23,produce_time为2018-09-10,car_type为电车
<update id="updateCarByPOJO">
update t_car set
car_num = #{carNum}, brand = #{brand},
guide_price = #{guidePrice}, produce_time = #{produceTime},
car_type = #{carType}
where id = #{id}
</update>
@Test
public void testUpdateCarById_POJO(){
// 准备数据
Car car = new Car();
car.setId(5L);
car.setCarNum("102");
car.setBrand("比亚迪汉");
car.setGuidePrice(30.23);
car.setProduceTime("2018-09-10");
car.setCarType("电车");
// 获取SqlSession对象
SqlSession sqlSession = SqlSessionUtil.openSession();
// 执行SQL语句
int count = sqlSession.update("updateCarById_Map", car);
System.out.println("更新了几条记录:" + count);
}
1.3.2 用Map传参Update
项目结构:
实体类不必再说;
Mapper接口中抽象方法的声明(Mybatis 中的 Mapper 接口相当于以前的 Dao。但是区别在于,Mapper 仅仅只是建接口即可,我们不需要提供实现类,具体的SQL写到对应的Mapper文件)
EmployeeMapper接口:
int updateEmployeeByMap(Map<String, Object> paramMap);
SQL语句
<update id="updateEmployeeByMap">
update t_emp set emp_salary=#{empSalaryKey} where emp_id=#{empIdKey}
</update>
测试程序
@Test
public void testUpdateEmpNameByMap() {
// 获取SqlSession对象
SqlSession sqlSession = SqlSessionUtil.openSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("empSalaryKey", 999.99);
paramMap.put("empIdKey", 5);
int result = mapper.updateEmployeeByMap(paramMap);
log.info("result = " + result);
}
1.4 select
1.4.1 select(Retrieve)一条
select语句和其它语句不同的是:查询会返回一个结果集ResultSet。,然而,语句是:
Object car = sqlSession.selectOne("selectCarById", 1);
因此,必须将ResultSet转化成成一个Java对象。mybatis查询之后返回一个Java对象的话,至少你要告诉mybatis返回一个什么类型的Java对象,可以在标签中添加resultType属性,用来指定查询要转换的类型:
<select id="selectCarById" resultType="com.sunsplanter.mybatis.pojo.Car">
select * from t_car where id = #{id}
</select>
@Test
public void testSelectCarById(){
// 获取SqlSession对象
SqlSession sqlSession = SqlSessionUtil.openSession();
//查一条记录用selectOne方法
Car car = sqlSession.selectOne("selectCarById", 1);
System.out.println(car);
}
然而,这样输出的结果是:
原因是:例如对于汽车编号这个字段,在POJO类和mapper文件中为carNum,但在数据库表中为car_num,这样当然查不到。
改为:
<!--虽然结果是List集合,但是resultType属性需要指定的是List集合中元素的类型。-->
<select id="selectCarById" resultType="com.sunsplanter.mybatis.pojo.Car">
select
<!--记得使用as起别名,让查询结果的字段名和java类的属性名对应上。
方法是:数据库中字段名 as Java中属性名-->
id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType
from
t_car
where
id = #{id}
</select>
1.4.2 select(Retrieve)多条**
- 取一条时,用selectOne方法返回ResultSet,转化成一个指定对象,这个指定对象只要定义个Car car接受即可。
取多条时,用selectList方法返回多个ResultSet转换成多个指定对象,这多个对象定义一个List来接受,否则要定义Car car1 car2…
<!--虽然结果是List集合,但是resultType属性需要指定的是List集合中元素的类型。-->
<select id="selectCarAll" resultType="com.sunsplanter.mybatis.pojo.Car">
<!--记得使用as起别名,让查询结果的字段名和java类的属性名对应上。-->
select
id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType
from
t_car
</select>
@Test
public void testSelectCarAll(){
// 获取SqlSession对象
SqlSession sqlSession = SqlSessionUtil.openSession();
// 执行SQL语句
List<Object> cars = sqlSession.selectList("selectCarAll");
// 输出结果
cars.forEach(car -> System.out.println(car));
}
2 (Mapper接口中编写SQL)完成CRUD
2.1 实体类:
@Data
public class Article {
private Integer id;
private Integer userId;
private String title;
private String summary;
private Integer readCount;
private LocalDateTime createTime;
private LocalDateTime updateTime;
2.2 ArticleMapper.java接口:
public interface ArticleMapper {
String
field_list="id,user_id,title,summary,read_count,create_time,update_time";
@Insert("""
insert into
article(user_id,title,summary,read_count,create_time,update_time) \
values(#{userId},#{title},#{summary},#{readCount},#{createTime},#{updateTime})
""")
int insertArticle(Article article);
@Update("""
update article set read_count=#{num} where id=#{id}
""")
int updateReadCount(Integer id,Integer num);
@Delete("""
delete from article where id=#{id}
""")
int deleteById(Integer id);
/*
*若将SQL写入mapper接口,一个查询操作应包含三部分内容
1.SQL语句 2.结果映射 3.方法声明
*3: 将实体类字段articleId映射到数据库字段id, 调用selectById方法并传入参数, 最终返回一个Article对象
*/
@Select("select " + field_list + " from article where id=#{articleId}")
@Results({
@Result(id = true,column = "id",property = "id"),
@Result(column = "user_id",property = "userId"),
@Result(column = "read_count",property = "readCount"),
@Result(column = "create_time",property = "createTime"),
@Result(column = "update_time",property = "updateTime"),
})
Article selectById(@Param("articleId") Integer id)
}
2.3 启动类加入扫描注解, 配置数据源
@MapperScan({"com.sunsplanter.mapper"})
@SpringBootApplication
public class Lession10MyBatisApplication {
public static void main(String[] args) {
SpringApplication.run(Lession10MyBatisApplication.class, args);
}
}
2.4 单元测试
@SpringBootTest
class MyBatisApplicationTests {
@Autowired
private ArticleMapper articleMapper;
@Test
void testInsert() {
ArticlePO article = new ArticlePO();
article.setTitle("什么时候用微服务");
article.setSummary("微服务优缺点");
article.setUserId(219);
article.setReadCount(560);
article.setCreateTime(LocalDateTime.now());
article.setUpdateTime(LocalDateTime.now());
articleMapper.insertArticle(article);
}
@Test
void testUpdate() {
int rows = articleMapper.updateReadCount(1, 230);
System.out.println("修改的记录数量:" + rows);
}
@Test
void testDelete(){
int rows = articleMapper.deleteById(11);
System.out.println("删除记录的数量 " + rows);
}
@Test
void testSelect(){
ArticlePO articlePO = articleMapper.selectById(3);
System.out.println("查询到的文章:" + articlePO);
}
}
2.5 (Mapper接口中编写SQL)的优化
尽管我们能在方法上面直接编写 SQL 语句 , 但不够简洁。
MyBatis 提供了 SQL 提供者的功能。将 SQL 以方法的形式定义在单独的类中。 Mapper 接口通过引用 SQL 提供
者中的方法名称,声明要执行的 SQL。
SQL 提供者有四类@SelectProvider,@InsertProvider,@UpdateProvider,@DeleteProvider。
其格式是: @Select/Insert…Provider(type = 提供者类.class, method = “方法名称”)
2.5.1 创建 SQL 提供者
public class SqlProvider {
public static String selectArticle(){
return """
select id,user_id,title,summary,read_count,create_time,update_time
from article where id=#{articleId}
""";
}
}
2.5.2 创建 mapper 接口
public interface ArticleMapper {
@Select("")
@Results(id = "BaseMapper", value = {
@Result(id = true, column = "id", property = "id"),
@Result(column = "user_id", property = "userId"),
@Result(column = "read_count", property = "readCount"),
@Result(column = "create_time", property = "createTime"),
@Result(column = "update_time", property = "updateTime"),
})
Article articleMapper();
//查询
@ResultMap("BaseMapper")
@SelectProvider(type = SqlProvider.class,method = "selectArticle")
Article selectById(Integer id);
}
3 起别名简化xxxMapper中的resultType
在UserMapper.xml中, 如果返回的类型指定为一个已定义类, 则resultType会比较繁琐:
<select id="selectById" resultType="com.sunsplanter.pojo.User">
SELECT id, username, password FROM user WHERE id=#{id}
</select>
解决办法: 在mybatis-config.xml中提前用typeAliases标签起别名:
<typeAliases>
<typeAlias type="com.sunsplanter.pojo.User" alias="User"/>
</typeAliases>
此时UserMapper.xml中可以改为:
<select id="selectById" resultType="User">
SELECT id, username, password FROM user WHERE id=#{id}
</select>
5 命名空间
在SQL Mapper配置文件中标签的namespace属性可以翻译为命名空间,这个命名空间主要是为了防止sql id冲突的。
我们知道一张表对应一个Mapper.xml,而在程序中,仅仅以语句块的id作为辨识符,决定执行哪个语句。例如:
CarMapper1.xml:
<mapper namespace="car1">
<select id="selectCarAll" resultType="com.sunsplanter.mybatis.pojo.Car">
select
id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType
from
t_car
</select>
</mapper>
此时如有CarMapper2.xml:
<mapper namespace="car2">
<select id="selectCarAll" resultType="com.sunsplanter.mybatis.pojo.Car">
select
id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType
from
t_car
</select>
</mapper>
而代码为:
@Test
public void testNamespace(){
// 获取SqlSession对象
SqlSession sqlSession = SqlSessionUtil.openSession();
// 执行SQL语句
List<Object> cars = sqlSession.selectList("selectCarAll");
// 输出结果
cars.forEach(car -> System.out.println(car));
}
就会报错,因为两个Mapper中都有id为“selectCarAll”的Sql语句段,系统无法分辨执行哪一段。
解决办法:“namespace.id”
List<Object> cars = sqlSession.selectList("car2.selectCarAll");
6 输出数据
项目结构:
6.1 准备表和实体类
准备一张名为 user 的表。该表包含字段 id(主键)、username、password
准备pojo类:
@Data //lombok生成set/get/构造方法
public class User {
private Integer id;
private String username;
private String password;
}
6.2 定义一个 Mapper 接口 UserMapper
,并在其中添加 user 表的增、删、改、查方法。
public interface UserMapper {
int insert(User user);
int update(User user);
int delete(Integer id);
User selectById(Integer id);
List<User> selectAll();
}
6.3 编写UserMapper.xml
在 resources /mappers目录下创建一个名为 UserMapper.xml
的 XML 文件,包含与 Mapper 接口中相同的五个 SQL 语句,并在其中,将查询结果映射到 User
实体中。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace等于mapper接口类的全限定名,这样实现对应 -->
<mapper namespace="com.atguigu.mapper.UserMapper">
<!-- 定义一个插入语句,并获取主键值 -->
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user(username, password)
VALUES(#{username}, #{password})
</insert>
<update id="update">
UPDATE user SET username=#{username}, password=#{password}
WHERE id=#{id}
</update>
<delete id="delete">
DELETE FROM user WHERE id=#{id}
</delete>
<!-- resultType使用user别名,稍后需要配置!-->
<select id="selectById" resultType="user">
SELECT id, username, password FROM user WHERE id=#{id}
</select>
<!-- resultType返回值类型为集合,所以只写范型即可! -->
<select id="selectAll" resultType="user">
SELECT id, username, password FROM user
</select>
</mapper>
6.4 编写mybatis-config.xml配置文件
<?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>
<settings>
<!-- 开启驼峰式映射-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 开启logback日志输出-->
<setting name="logImpl" value="SLF4J"/>
</settings>
<typeAliases>
<!-- 给实体类起别名 -->
<package name="com.atguigu.pojo"/>
</typeAliases>
<!-- environments表示配置Mybatis的开发环境,可以配置多个环境,在众多具体环境中,使用default属性指定实际运行时使用的环境。default属性的取值是environment标签的id属性的值。 -->
<environments default="development">
<!-- environment表示配置Mybatis的一个具体的环境 -->
<environment id="development">
<!-- Mybatis的内置的事务管理器 -->
<transactionManager type="JDBC"/>
<!-- 配置数据源 -->
<dataSource type="POOLED">
<!-- 建立数据库连接的具体信息 -->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- Mapper注册:从Resources开始查找,此时已经放进mappers文件夹了,因此写全 -->
<mapper resource="mappers/UserMapper.xml"/>
</mappers>
</configuration>
6.5 测试程序
package com.atguigu.test;
import com.atguigu.mapper.UserMapper;
import com.atguigu.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.util.List;
/**
* projectName: com.atguigu.test
*/
public class MyBatisTest {
private SqlSession sqlSession;
// junit会在每一个@Test方法前执行@BeforeEach方法
@BeforeEach
public void init() throws IOException {
sqlSession = new SqlSessionFactoryBuilder()
.build(
Resources.getResourceAsStream("mybatis-config.xml"))
.openSession();
}
@Test
public void createTest() {
User user = new User();
user.setUsername("admin");
user.setPassword("123456");
UserMapper userMapper = session.getMapper(UserMapper.class);
userMapper.insert(user);
System.out.println(user);
}
@Test
public void updateTest() {
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.selectById(1);
user.setUsername("root");
user.setPassword("111111");
userMapper.update(user);
user = userMapper.selectById(1);
System.out.println(user);
}
@Test
public void deleteTest() {
UserMapper userMapper = session.getMapper(UserMapper.class);
userMapper.delete(1);
User user = userMapper.selectById(1);
System.out.println("user = " + user);
}
@Test
public void selectByIdTest() {
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.selectById(1);
System.out.println("user = " + user);
}
@Test
public void selectAllTest() {
UserMapper userMapper = session.getMapper(UserMapper.class);
List<User> userList = userMapper.selectAll();
System.out.println("userList = " + userList);
}
// junit会在每一个@Test方法后执行@@AfterEach方法
@AfterEach
public void clear() {
session.commit();
session.close();
}
}