第五章 Spring Boot的数据库编程

若有错,请指出
第二章 搭建Springboot环境,配置视图解析器jsp页面
第三章 全注解下的Spring Ioc
第四章 约定编程-Spring AOP
第五章 Spring Boot的数据库编程

5.1 配置数据源

5.1.1 配置默认数据源

H2是内嵌式数据库,可以不使用任何配置数据库的情况下(不用写drivername,username,password等详细信息)运行项目工程
新建springboot工程,添加如下配置(添加内存数据库H2,Spring Data JPA用于配置数据源)
在这里插入图片描述
目录结构
在这里插入图片描述
控制层类

@Controller
@RequestMapping("/db")
public class DatabaseController {
    @Autowired
    private DataSource dataSource=null;

    @RequestMapping("/info")
    @ResponseBody//返回json数据到页面上
    public Map<String ,Object> info(){
        Map<String,Object> map=new HashMap<>();
        Connection connection=null;
        try {
            connection=dataSource.getConnection();
            //DatabaseMetaData接口可以获得有关已连接到的数据库(内存数据库H2)的元数据
            DatabaseMetaData metaData=connection.getMetaData();
            map.put("version",metaData.getDatabaseProductVersion());
            map.put("url",metaData.getURL());
            map.put("product_name",metaData.getDatabaseProductName());
            map.put("user_name",metaData.getUserName());
        }catch (SQLException e){
            e.printStackTrace();//打印异常信息
        }finally {
            if(connection!=null) {
                try {
                    connection.close();//关闭连接
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        return map;
    }
}

运行截图
在这里插入图片描述

5.1.2 配置自定义数据源

1.配置MySQL自定义数据源
这里需要删h2的依赖,添加MySQL的依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>`

还需要配置数据库的相关信息才能连接到数据库,增加application.properties信息

spring.datasource.url=jdbc:mysql://localhost:3306/chapter5
spring.datasource.username=root
spring.datasource.password=123456
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#最大等待连接中的数量,设0为没有限制
spring.datasource.tomcat.max-idle=10
#最大连接活动数
spring.datasource.tomcat.max-active=50
#最大等待毫秒数,单位ms,超过时间会出错误信息
spring.datasource.tomcat.max-wait=10000
#数据库连接池初始化连接数
spring.datasource.tomcat.initial-size=5

虽然上面注释掉了驱动类的配置,但是还是可以连接数据源,因为Spring Boot会尽可能判断数据源是什么类型,然后根据默认情况匹配驱动类。如果它不能匹配,我们就可以明确的配置它。因为这里是使用Tomcat自动的数据库连接池,所以可以看到上面代码中有许多tomcat字样。

#spring.datasource.driver-class-name=com.mysql.jdbc.Driver

创建数据库chapter5
在这里插入图片描述
运行截图
在这里插入图片描述
2.配置第三方DBCP2数据源
上面绑定的是Tomcat的数据源,这里我们也可以使用第三方数据源,如DBCP2数据源。
在这里插入图片描述
上图中绑定的是Tomcat的数据源,若希望使用第三方数据源则要修改如下信息
引入DBCP数据源依赖

		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-dbcp2</artifactId>
		</dependency>

修改application.properties

spring.datasource.url=jdbc:mysql://localhost:3306/chapter5
spring.datasource.username=root
spring.datasource.password=123456
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#指定数据库连接池的类型
spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource
#最大等待连接中的数量,设0为没有限制
spring.datasource.dbcp2.max-idle=10
#最大连接活动数
spring.datasource.dbcp2.max-total=50
#最大等待毫秒数,单位炜ms,超过时间会出错误信息
spring.datasource.dbcp2.max-wait-millis=10000
#数据库连接池初始化连接数
spring.datasource.dbcp2.initial-size=5

断点debug,输入http://localhost:8080/db/info,这就是第三方数据源
在这里插入图片描述

5.2 JDBC的缺陷

下面是使用Jdbc操作数据库的案例
目录结构
在这里插入图片描述
注释掉dbcp的依赖和配置,使用MySQL作为自定义数据源,不然的话注解上会出现无法自动装配。有多个类型为 ‘DataSource’ 的 bean,虽然不影响结果执行。

在chapter5数据库中创建表和插入数据
comment字段是注释作用,check是约束作用,属性名的范围在1和2之间

create table t_food(
	id int(12) auto_increment,
	food_name varchar(20) not null,
	food_type int(3) not null
		comment '1-生食,2-熟食'
		check(product_type in (1,2)),
	note varchar(512) null,
	primary key(id)
);
//插入数据
insert into t_food values(1,'椰子鸡',2,'海南菜');
insert into t_food values(2,'宫爆鸡丁',2,'四川菜');
insert into t_food values(3,'肉夹馍',2,'陕西菜');
insert into t_food values(4,'榨菜',1,'广东菜');
insert into t_food values(5,'生鸡蛋',1,'广西菜');

创建枚举类

在这里插入代码片//枚举类
public enum FoodTypeEnum {
    COOKED(2,"熟食"),RAW(1,"生食");
    private int code;
    private String name;
    FoodTypeEnum(int code,String name){
        this.code=code;
        this.name=name;
    }
    //根据code获取食物类型
    public static FoodTypeEnum getFoodTypeEnum(int code){
        for(FoodTypeEnum foodTypeEnum:FoodTypeEnum.values()){
            if(foodTypeEnum.getCode()==code)return foodTypeEnum;
        }
        return null;
    }
	//右键生成getter和setter方法
}

创建Food的po对象用于开发人员使用比较难理解,我们可以创建VO对象(View Object)给外部人员看
Food类

public class Food {
    private Long id;
    private String foodName;
    private FoodTypeEnum foodTypeEnum;
    private String note;
//右键生成getter和setter方法
}

FoodVo类

//View Object:视图对象给用户看,pojo对象给内部开发人员看,因为有个枚举类型的属性难理解所以用vo简化含义
public class FoodVo {
    private Long id;
    private String foodName;
    private int foodTypeId;
    private String foodTypeName;
    private String note;
//右键生成getter和setter方法
}

服务类JdbcService接口和实现类

public interface JdbcService {
    //根据id查找食物
    public Food getFood(Long id);
}

@Service
public class JdbcServiceImpl implements JdbcService {
    @Autowired//注入数据源
    DataSource dataSource=null;

    //获取食物
    @Override
    public Food getFood(Long id) {
        Food food=null;
        Connection connection=null;
        try{
            connection=dataSource.getConnection();
            //定义sql语句
            String sql="select id,food_name,food_type,note from t_food where id=?";
            //PreparedStatement可以有效防止sql注入,所以生产环境上一定要使用PreparedStatement,而不能使用Statement
            //创建一个PreparedStatement对象,用于将参数化SQL语句发送到数据库
            PreparedStatement preparedStatement=connection.prepareStatement(sql);
            preparedStatement.setLong(1,id);
            //返回数据库查询结果存在ResultSet对象供我们使用
            ResultSet resultSet=preparedStatement.executeQuery();
            while (resultSet.next()){
                Long fid=resultSet.getLong("id");
                String foodName=resultSet.getString("food_name");
                int foodType=resultSet.getInt("food_type");
                String note=resultSet.getString("note");
                food=new Food();
                food.setId(fid);
                food.setFoodName(foodName);
                food.setNote(note);
                food.setFoodTypeEnum(FoodTypeEnum.getFoodTypeEnum(foodType));
            }
        }catch (SQLException e){
            e.printStackTrace();
        }finally {
            if (connection!=null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        return food;
    }
}

DatabaseController类添加如下代码

    //将Food对象变为FoodVo对象
    public  FoodVo changeToVo(Food food){
        FoodVo foodVo=new FoodVo();
        foodVo.setId(food.getId());
        foodVo.setFoodTypeId(food.getFoodTypeEnum().getCode());
        foodVo.setFoodTypeName(food.getFoodTypeEnum().getName());
        foodVo.setFoodName(food.getFoodName());
        foodVo.setNote(food.getNote());
        return foodVo;
    }
    //获取数据库的食物信息,路劲还要自己添加id参数值
    @RequestMapping("/food")
    @ResponseBody
    public FoodVo getFood(Long id){
        Food food=jdbcService.getFood(id);
        return changeToVo(food);
    }

运行截图
在这里插入图片描述
通过这个例子可以看出jdbc的操作问题
在这里插入图片描述

5.3 JdbcTemplate操作数据库

JdbcTemplate这种方式也不算成功,实际工作较少用到。
在JdbcServiceImpl添加如下代码,控制层调用该方法即可运行了

    //之前已经引入jdbc依赖了
    @Autowired
    private JdbcTemplate jdbcTemplate=null;
    //通过JdbcTemplate获取食物
    @Override
    public Food getFood2(Long id) {
        String sql="select id,food_name,food_type,note from t_food where id=?";
        //通过实现RowMapper接口完成JdbcTemplate(即数据库到pojo对象的映射关系)
        //由于RowMapper只有一个方法也就是函数式接口,可以用lambda表达式实现接口而不用另建类,比较优雅
        RowMapper<Food> rowMapper=(rs,rowNum)->{
            Long fid=rs.getLong("id");
            String foodName=rs.getString("food_name");
            int foodType=rs.getInt("food_type");
            String note=rs.getString("note");
            Food food=new Food();
            food.setId(fid);
            food.setFoodName(foodName);
            food.setNote(note);
            food.setFoodTypeEnum(FoodTypeEnum.getFoodTypeEnum(foodType));
            return food;
        };
        //只返回一条记录,一般用于聚合函数的查询
        Food food=jdbcTemplate.queryForObject(sql,rowMapper,id);
        return food;
    }

query(String sql, RowMapper rowMapper) 可以返回一组记录
queryForObject(String sql, RowMapper rowMapper, Object… args) 只返回一条记录,因此一般用于聚合函数的查询

5.4 JPA编程

在这里插入图片描述
在这里插入图片描述
从图中可以看出JPA最顶级的接口时Repository,没有定义任何方法,而JPA接口扩展JpaRepository包含了其他子接口的方法,所以只需要定义JpaRepository就可以对数据库实行增删改查等一系列操作

5.4.1 案例

目录结构在这里插入图片描述

引入jpa依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

JPA维护的核心是实体(Entity Bean),我们修改Foodr类

//声明实体
@Entity(name = "food")
//映射的数据库表名
@Table(name = "t_food")
public class Food {
    //标明为主键
    @Id
    //表明主键策略,递增
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    //food_name是数据库名,与属性名不一致需要添加@Column进行映射
    @Column(name = "food_name")
    private String foodName;
    //由于FoodTypeEnum类型是我们自定义的在数据据库表中不存在这类型,需要转换
    @Convert(converter = FoodTypeEnumConverter.class)
    @Column(name = "food_type")
    private FoodTypeEnum foodTypeEnum;
    //与数据库中的类型和属性名一致,自动映射到数据库中
    private String note;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getFoodName() {
        return foodName;
    }

    public void setFoodName(String foodName) {
        this.foodName = foodName;
    }

    public FoodTypeEnum getFoodTypeEnum() {
        return foodTypeEnum;
    }

    public void setFoodTypeEnum(FoodTypeEnum foodTypeEnum) {
        this.foodTypeEnum = foodTypeEnum;
    }

    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note;
    }
}

FoodTypeEnum转换器

//FoodTypeEnum转换器
//将实体类的属性FoodTypeEnum类型转换为数据库的整型Integer(存的是FoodTypeEnum的code属性值)
//AttributeConverter<X, Y>,X为实体类型,Y为数据库类型
public class FoodTypeEnumConverter implements AttributeConverter<FoodTypeEnum,Integer> {
    @Override
    public Integer convertToDatabaseColumn(FoodTypeEnum foodTypeEnum) {
        return foodTypeEnum.getCode();
    }
    @Override
    public FoodTypeEnum convertToEntityAttribute(Integer code) {
        return FoodTypeEnum.getFoodTypeEnum(code);
    }
}

然后就可以定义Jpa接口扩展JpaRepository便可以获得Jpa提供的方法,默认帮我们实现方法即不用自己写实现类。
interface JpaRepository<T, ID>中,T指的是类型,id是主键的数据类型
新建与数据库数据交接的dao接口,用它继承的Jpa扩展接口JpaRepository许多封装的方法来操作数据库

//实现Jpa扩展接口
@Repository
public interface JpaFoodDao extends JpaRepository<Food, Long> {
}

创建服务JpaService接口和实现类
Spring Data JPA 中 getOne、findById、findOne 的区别

public interface JpaService {
    public Food getFood(Long id);
    public Food insertFood(Food food);
    public void deleteFood(Long id);
    public Food updateFood(Food food);
    public List<Food> findFood(String foodName, FoodTypeEnum foodTypeEnum);
}

@Service
public class JpaServiceImpl implements JpaService {
    @Autowired
    JpaFoodDao jpaFoodDao=null;
    @Override
    public Food getFood(Long id) {
        return jpaFoodDao.getById(id);
    }

    @Override
    public Food insertFood(Food food) {
        return jpaFoodDao.save(food);
    }
	//我这里只能删除本来有的菜,若给存在的id页面会报错
    @Override
    public void deleteFood(Long id) {
        jpaFoodDao.deleteById(id);
    }

    @Override
    public Food updateFood(Food food) {
        return jpaFoodDao.save(food);
    }

    @Override
    public List<Food> findFood(String foodName, FoodTypeEnum foodTypeEnum) {
        Food food=new Food();
        if(!StringUtils.isEmpty(foodName))
        food.setFoodName(foodName);
        if(foodTypeEnum!=null)
        food.setFoodTypeEnum(foodTypeEnum);
        //Example按例查询允许动态创建查询,并且不需要编写包含字段名称的查询。不需要使用特定的数据库的查询语言来编写查询语句
        Example<Food> example=Example.of(food);
        return jpaFoodDao.findAll(example);
    }
}

创建控制层

@Controller
@RequestMapping("/jpa")
public class JpaController {
    // 注入JPA接口,这里不需要使用实现类
    @Autowired
    private JpaService jpaService = null;

    public Map<String ,Object> resultMap(Boolean success,String msg){
        Map<String ,Object> map=new HashMap<>();
        map.put("success:",success);
        map.put("msg:",msg);
        return map;
    }
    //将Food对象变为FoodVo对象
    public  FoodVo changeToVo(Food food){
        FoodVo foodVo=new FoodVo();
        foodVo.setId(food.getId());
        foodVo.setFoodTypeId(food.getFoodTypeEnum().getCode());
        foodVo.setFoodTypeName(food.getFoodTypeEnum().getName());
        foodVo.setFoodName(food.getFoodName());
        foodVo.setNote(food.getNote());
        return foodVo;
    }

    //将Food对象数组变为FoodVo对象数组
    public  List<FoodVo> changeToVoList(List<Food> foodList){
        List<FoodVo> foodVoList=new ArrayList<>();
        for(int i=0;i<foodList.size();i++){
            FoodVo foodVo=new FoodVo();
            Food food=foodList.get(i);
            foodVo.setId(food.getId());
            foodVo.setFoodTypeId(food.getFoodTypeEnum().getCode());
            foodVo.setFoodTypeName(food.getFoodTypeEnum().getName());
            foodVo.setFoodName(food.getFoodName());
            foodVo.setNote(food.getNote());
            foodVoList.add(foodVo);
        }
        return foodVoList;
    }

    @RequestMapping("/food/get")
    @ResponseBody
    public FoodVo getFood(Long id) {
        // 使用JPA接口查询对象
        Food food = jpaService.getFood(id);
        return changeToVo(food);
    }

    @RequestMapping("/food/insert")
    @ResponseBody
    public FoodVo insertFood(String foodName, Integer foodTypeCode,String note) {
        Food food=new Food();
        food.setFoodName(foodName);
        food.setFoodTypeEnum(FoodTypeEnum.getFoodTypeEnum(foodTypeCode));
        food.setNote(note);
        jpaService.insertFood(food);
        return changeToVo(food);
    }

    @RequestMapping("/food/delete")
    @ResponseBody
    public Map<String ,Object> deleteFood(Long id){
        Food food=this.jpaService.getFood(id);
        if(food!=null){
            this.jpaService.deleteFood(id);
            return this.resultMap(true,"删除成功【"+id+"】");
        }
        return resultMap(false,"删除失败【"+id+"】不存在");
    }

    @RequestMapping("/food/update")
    @ResponseBody
    public FoodVo updateFood(Long id,String foodName,Integer foodTypeCode,String note){
        Food food=new Food();
        food.setFoodName(foodName);
        food.setFoodTypeEnum(FoodTypeEnum.getFoodTypeEnum(foodTypeCode));
        food.setNote(note);
        food.setId(id);
        jpaService.updateFood(food);
        return changeToVo(food);
    }

    @RequestMapping("/food/find")
    @ResponseBody
    public List<FoodVo> findFood(String foodName,Integer foodTypeCode){
        FoodTypeEnum foodTypeEnum=null;
        if(foodTypeCode!=null)
            foodTypeEnum=FoodTypeEnum.getFoodTypeEnum(foodTypeCode);
        List<Food> foodList=jpaService.findFood(foodName,foodTypeEnum);
        return changeToVoList(foodList);
    }
}

在这里插入图片描述
删除菜品
在这里插入图片描述
插入新菜(shuru的路径是http://localhost:8080/jpa/food/update?id=6&foodName=‘辣子鸡’&foodTypeCode=1&note=‘四川菜’)
在这里插入图片描述

更新菜
在这里插入图片描述
查找菜(也可以foodTypeCode=2)
在这里插入图片描述

5.4.2 分页排序

分页排序来自扩展的PagingAndSortingRepository,两个方法参数上分别有Sort类和Pageable类
为了更好的查看数据库执行语句我们先配置JPA属性在application.properties上

#配置jpa属性
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
#打印数据库sql
spring.jpa.show-sql=true

接口Jpaservice

    //排序查找
    List<Food> findFoodBySort(String foodName,Integer foodTypeCode,String sortField);
    //分页排序
    Page<Food> findFoodByPage(String foodName,Integer foodTypeCode,String sortField,Integer page,Integer size);

JpaServiceImpl
传入的参数都进行非空判断,这样在输入url的时候只有部分参数赋值不会报错
Pageable的page是第几页,默认从0开始;size每一页的大小

    @Override//根据某一列字段排序查找
    public List<Food> findFoodBySort(String foodName, Integer foodTypeCode, String sortField) {
        Food food=new Food();
        if(!StringUtils.isEmpty(foodName))
            food.setFoodName(foodName);
        if(foodTypeCode!=null){
            FoodTypeEnum foodTypeEnum=FoodTypeEnum.getFoodTypeEnum(foodTypeCode);
            food.setFoodTypeEnum(foodTypeEnum);
        }
        //Example按例查询允许动态创建查询,并且不需要编写包含字段名称的查询。不需要使用特定的数据库的查询语言来编写查询语句
        Example<Food> example=Example.of(food);
        //默认升序
        //Sort sort=Sort.by(sortField);
        //降序
        Sort sort=null;
        if(sortField!=null)sort=Sort.by(Sort.Direction.DESC,sortField);
        return jpaFoodDao.findAll(example,sort);
    }

    @Override//分页排序
    public Page<Food> findFoodByPage(String foodName, Integer foodTypeCode, String sortField, Integer page, Integer size) {
        Food food=new Food();
        if(!StringUtils.isEmpty(foodName))
            food.setFoodName(foodName);
        if(foodTypeCode!=null){
            FoodTypeEnum foodTypeEnum=FoodTypeEnum.getFoodTypeEnum(foodTypeCode);
            food.setFoodTypeEnum(foodTypeEnum);
        }
        Example<Food> example=Example.of(food);
        Sort sort=null;
        if(sortField!=null)sort=Sort.by(sortField);
        Pageable pageable= PageRequest.of(page,size,sort);
        return jpaFoodDao.findAll(example,pageable);
    }

控制层JpaController

    @RequestMapping("/food/sort")
    @ResponseBody//排序查找
    public List<FoodVo> findFoodBySort(String foodName,Integer foodTypeCode,String sortField){
        List<Food> foodList=jpaService.findFoodBySort(foodName,foodTypeCode,sortField);
        return changeToVoList(foodList);
    }
    @RequestMapping("/food/page")
    @ResponseBody//分页排序
    public Page<Food> findFoodByPage(String foodName, Integer foodTypeCode, String sortField, Integer page, Integer size){
        Page<Food> foodList=jpaService.findFoodByPage(foodName,foodTypeCode,sortField,page,size);
        return foodList;
    }

运行截图
在这里插入图片描述

5.4 MyBatis简介

在这里插入图片描述在这里插入图片描述
这里主要描述settings、typeAliases、typeHandlers、plugins和mappers的应用
settings(设置):他的配置改变MyBatis的底层行为,配置映射规则如自动映射、驼峰映射
typeAliases(类型别名):应为使用类全限定名会比较长,所以MyBatis会对常用类提供默认的别名或则允许我们通过typeAliases自定义别名
typeHandlers(类型处理器):在MyBatis写入和读取数据库的过程中对于不同类型的数据(对于Java是JavaType,对于数据库是JdbcType)进行自定义转换,在大部分情况下我们不需要使用自定义的typeHandler,因为MyBatis自身定义了较多的typeHandler,会自动识别JavaType和jdbcType从而实现类型转换,一般使用在枚举类型上

案例

这里是在上章的基础上进行添加操作
目录结构
在这里插入图片描述
引入MyBatis依赖

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.1</version>
        </dependency>

定义类别名,这样子在mapper.xml文件需要填resultType属性时就不用写类全限定名,比较简洁
在这里插入图片描述
由于实体Food食物类型属性是自定义枚举类型FoodTypeEnum,所以还需要创建FoodTypeEnumTypeHandler转换器类进行转换到数据库的整型(数据库存储的是枚举类型的code属性,1代表生食2代表熟食)

//用于Java的JavaType和数据库的JdbcType进行自定义转换
//声明数据库类型
@MappedJdbcTypes(value = JdbcType.INTEGER)
//声明Java类型
@MappedTypes(value = FoodTypeEnum.class)
public class FoodTypeEnumTypeHandler extends BaseTypeHandler<FoodTypeEnum> {
    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, FoodTypeEnum foodTypeEnum, JdbcType jdbcType) throws SQLException {
    preparedStatement.setInt(i,foodTypeEnum.getCode());
    }

    @Override
    public FoodTypeEnum getNullableResult(ResultSet resultSet, String columnName) throws SQLException {
        int code=resultSet.getInt(columnName);
        return FoodTypeEnum.getFoodTypeEnum(code);
    }

    @Override
    public FoodTypeEnum getNullableResult(ResultSet resultSet, int columnIndex) throws SQLException {
        int code=resultSet.getInt(columnIndex);
        return FoodTypeEnum.getFoodTypeEnum(code);
    }

    @Override
    public FoodTypeEnum getNullableResult(CallableStatement callableStatement, int columnIndex) throws SQLException {
        int code=callableStatement.getInt(columnIndex);
        return FoodTypeEnum.getFoodTypeEnum(code);
    }
}

创建dao层MyBatisFoodDao接口和mapper文件,注解@Mapper和@Repository两者都可以用。
@Repository需要在Spring中配置扫描地址,然后生成Dao层的Bean才能被注入到Service层中。
@Mapper不需要配置扫描地址,通过xml里面的namespace里面的接口地址,生成了Bean后注入到Service层中。
@Mapper注解:在接口类上添加了@Mapper,在编译之后会生成相应的接口实现类
@MapperScan注解:指定要变成实现类的接口所在的包,然后包下面的所有接口在编译之后都会生成相应的实现类。如果想要每个接口都要变成实现类,那么需要在每个接口类上加上@Mapper注解,比较麻烦,解决这个问题用@MapperScan

//@Repository//也可以
//标识为MyBatis的mapper
//@Mapper
public interface MyBatisFoodDao {
    public Food getFood(Long id);
    public int deleteFood(Long id);
    public int updateFood(Food food);
    public int insertFood(Food food);
    public List<Food> findFood(Food food);
}

然后编写dao层对应的mapper.xml文件

  1. id:对应dao层的方法名
  2. parpaeterType:对应dao层的参数名数据类型
  3. resultType:sql映射文件中定义返回值类型,在getFood和findFood方法中返回值是具体对象类型,reslutType的值本应该是全限定名类型,但是前面加了@Alians所以只需写food就可以了
  4. #{。。。}:里面写的是Food类的属性名,由于数据库名和Java属性名不一致,所以取数据库值赋值到Java属性值需要food_name as foodName
    resultType详解
<?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="com.springboot.chapter5.dao.MyBatisFoodDao">
    <select id="getFood" parameterType="long" resultType="food">
        select id,food_name as foodName,food_type as foodTypeEnum,note from t_food where id=#{id}
    </select>
    <delete id="deleteFood" parameterType="Long">
        delete from t_food where id=#{id}
    </delete>
    <update id="updateFood" parameterType="Long">
        update t_food
        <set>
            <if test="foodName!=null">food_name=#{foodName} ,</if>
            <if test="foodTypeEnum!=null">food_type=#{foodTypeEnum,typeHandler=com.springboot.chapter5.typehandler.FoodTypeEnumTypeHandler},</if>
            <if test="note!=null">note=#{note}</if>
        </set>
        where id=#{id}
    </update>
    <insert id="insertFood" parameterType="food" useGeneratedKeys="true" keyProperty="id">
        insert into t_food(food_name,food_type,note) values(#{foodName},
        #{foodTypeEnum,typeHandler=com.springboot.chapter5.typehandler.FoodTypeEnumTypeHandler},#{note})
    </insert>
    <select id="findFood" resultType="food" parameterType="food">
        select id,food_name as foodName,food_type as foodTypeEnum,note from t_food
        <where>
            <if test="foodName!=null">food_name like concat('%',#{foodName},'%') </if>
            <if test="foodTypeEnum!=null">and food_type=#{foodTypeEnum
            ,typeHandler=com.springboot.chapter5.typehandler.FoodTypeEnumTypeHandler} </if>
        </where>
    </select>
</mapper>

服务接口MyBatisService和实现类

public interface MyBatisService {
    public Food getFood(Long id);
    public int deleteFood(Long id);
    public int updateFood(Food food);
    public int insertFood(Food food);
    public List<Food> findFood(Food food);
}

@Service
public class MyBatisServiceImpl implements MyBatisService {
    @Autowired//启动类中就装配了
    MyBatisFoodDao myBatisFoodDao=null;
    @Override
    public Food getFood(Long id) {
        return myBatisFoodDao.getFood(id);
    }

    @Override
    public int deleteFood(Long id) {
        return myBatisFoodDao.deleteFood(id);
    }

    @Override
    public int updateFood(Food food) {
        return myBatisFoodDao.updateFood(food);
    }

    @Override
    public int insertFood(Food food) {
        return myBatisFoodDao.insertFood(food);
    }

    @Override
    public List<Food> findFood(Food food) {
        return myBatisFoodDao.findFood(food);
    }
}

控制层

@RequestMapping("/mybatis")
@Controller
public class MyBatisController {
    @Autowired
    MyBatisService myBatisService=null;
    //反馈是否执行成功的信息
    public Map<String ,Object> resultMap(String msg,Boolean success){
        Map<String ,Object> map=new HashMap<>();
        map.put("success:",success);
        map.put("msg:",msg);
        return map;
    }
    //将Food对象变为FoodVo对象
    public FoodVo changeToVo(Food food){
        FoodVo foodVo=new FoodVo();
        foodVo.setId(food.getId());
        foodVo.setFoodTypeId(food.getFoodTypeEnum().getCode());
        foodVo.setFoodTypeName(food.getFoodTypeEnum().getName());
        foodVo.setFoodName(food.getFoodName());
        foodVo.setNote(food.getNote());
        return foodVo;
    }
    //将Food对象数组变为FoodVo对象数组
    public  List<FoodVo> changeToVoList(List<Food> foodList){
        List<FoodVo> foodVoList=new ArrayList<>();
        for(int i=0;i<foodList.size();i++){
            FoodVo foodVo=new FoodVo();
            Food food=foodList.get(i);
            foodVo.setId(food.getId());
            foodVo.setFoodTypeId(food.getFoodTypeEnum().getCode());
            foodVo.setFoodTypeName(food.getFoodTypeEnum().getName());
            foodVo.setFoodName(food.getFoodName());
            foodVo.setNote(food.getNote());
            foodVoList.add(foodVo);
        }
        return foodVoList;
    }
    @RequestMapping("/food/get")
    @ResponseBody
    public FoodVo getFood(Long id){
        Food food=myBatisService.getFood(id);
        System.out.println(food);
        return changeToVo(food);
    }
    @RequestMapping("/food/delete")
    @ResponseBody
    public Map<String,Object> deleteFood(Long id){
        int result=myBatisService.deleteFood(id);
        Boolean success=result>0;
        String msg=success?"删除成功":"删除失败";
        return resultMap(msg,success);
    }
    @RequestMapping("/food/update")
    @ResponseBody
    public Map<String,Object> updateFood(Long id,String foodName,Integer foodTypeCode,String note){
        Food food=new Food();
        food.setId(id);
        food.setFoodName(foodName);
        food.setFoodTypeEnum(FoodTypeEnum.getFoodTypeEnum(foodTypeCode));
        food.setNote(note);
        int result=myBatisService.updateFood(food);
        Boolean success=result>0;
        String msg=success?"更新成功":"更新失败";
        return resultMap(msg,success);
    }
    @RequestMapping("/food/insert")
    @ResponseBody
    public FoodVo insertFood(String foodName,Integer foodTypeCode,String note){
        Food food=new Food();
        food.setFoodName(foodName);
        food.setFoodTypeEnum(FoodTypeEnum.getFoodTypeEnum(foodTypeCode));
        food.setNote(note);
        int result=myBatisService.insertFood(food);
        if(result>0)return changeToVo(food);
        throw new RuntimeException("插入异常");
    }
    @RequestMapping("/food/find")
    @ResponseBody
    public List<FoodVo> findFood(String foodName,Integer foodTypeCode){
        Food food=new Food();
        food.setFoodName(foodName);
        food.setFoodTypeEnum(FoodTypeEnum.getFoodTypeEnum(foodTypeCode));
        List<Food> foodList=myBatisService.findFood(food);
        return changeToVoList(foodList);
    }
}

配置aplication.properties,从而扫描映射文件、别名文件和typeHandler,同时为了更好的查看sql执行情况(出错时也可以查看sql语句哪里错)添加日志配置。mybatis提供了几种日志实现,这里我们选用STDOUT_LOGGING

mybatis.mapper-locations=classpath:com/springboot/chapter5/mapper/*.xml
mybatis.type-aliases-package=com.springboot.chapter5.pojo
mybatis.type-handlers-package=com.springboot.chapter5.typehandler

#控制台打印mybatis执行的sql语句
mybatis.configuration.log-impl= org.apache.ibatis.logging.stdout.StdOutImpl

使用MapperFactoryBean(针对一个接口配置)装配MyBatis接口,由于这里没写AppConfig配置类去构建IOC容器,这里把装配MyBatis的代码写在启动类中
SqlSessionTmplate是SqlSession的实现类,有了SqlSessionTemplate,我们就能用来执行Dao层的Sql语句而这个实现类中有一个关键的类就是SqlSessionFactory。
SqlSessionFactory就是生产SqlSession对象的工厂,是一个接口
SqlSession是客户端和数据库服务端之间的会话信息,里面有许多操作数据库的方法
三者详解

    @Autowired//绑定单个接口
    private SqlSessionTemplate sqlSessionTemplate=null;
    @Bean(name="myBatisFoodDao")
    public MapperFactoryBean<MyBatisFoodDao> initMyBatisFoodDao(){
        MapperFactoryBean<MyBatisFoodDao> myBatisFoodDaoMapperFactoryBean=new MapperFactoryBean<>();
        myBatisFoodDaoMapperFactoryBean.setSqlSessionTemplate(sqlSessionTemplate);
        myBatisFoodDaoMapperFactoryBean.setMapperInterface(MyBatisFoodDao.class);
        return myBatisFoodDaoMapperFactoryBean;
    }

这里运行文件后会出现无法找到mapper文件,由于mapper文件是放在Java包下,需要在pom.xml的添加如下配置
target是用来存放项目构建后的文件和目录、jar包、war包、编译的class文件,如果运行后没找到mapper文件说明映射出现问题常见整合mybatis错误 Invalid bound statement (not found)
在这里插入图片描述

        <resources>
            <resource>
                <directory>src/main/java</directory>
                <!-- 此配置不可缺,否则mybatis的Mapper.xml将会丢失 -->
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
            <!--指定资源的位置-->
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.yml</include>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>

增删改查运行截图
在这里插入图片描述
在这里插入图片描述

5.5 装配MyBatis接口的几种方式

方法一:使用MapperFactoryBean装配

    @Autowired//绑定单个接口
    private SqlSessionTemplate sqlSessionTemplate=null;
    @Bean(name="myBatisFoodDao")
    public MapperFactoryBean<MyBatisFoodDao> initMyBatisFoodDao(){
        MapperFactoryBean<MyBatisFoodDao> myBatisFoodDaoMapperFactoryBean=new MapperFactoryBean<>();
        myBatisFoodDaoMapperFactoryBean.setSqlSessionTemplate(sqlSessionTemplate);
        myBatisFoodDaoMapperFactoryBean.setMapperInterface(MyBatisFoodDao.class);
        return myBatisFoodDaoMapperFactoryBean;
    }

方法二:使用@MapperFactoryBean装配
前面使用MapperFactoryBean进行装配的,这里可以用扫描的方式整合比较简洁
现在MyBatisDao添加@Mapper注解,然后在启动类上注释掉之前的装配代码
在这里插入图片描述
方法三:使用@MapperScan装配
如果项目中不存在多个SqlSessionFactory(或者SqlSessionTemplat),那么完全可以不配置sqlSessionFactoryRef或者sqlSessionTemplateRef,如果存在多个则需要指定。而且sqlSessionTemplateRef的优先权gaoyusqlSessionFactoryRef

@MapperScan(basePackages = "com.springboot.chapter5.dao",annotationClass = Mapper.class,
                //指定SqlSessionFactory,如果sqlSessionTemplate被指定,则作废
                sqlSessionFactoryRef = "sqlSessionFactory",
                //指定sqlSessionTemplate,将忽略sqlSessionFactory的配置
                sqlSessionTemplateRef = "sqlSessionTemplate")

若能帮助您,不妨点个赞呗😀

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值