数据访问
准备数据库
CREATE DATABASE eshop;
USE eshop;
CREATE TABLE userinfo (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
userName VARCHAR(20) NOT NULL ,
userPass VARCHAR(48) NOT NULL,
birthday DATE DEFAULT NULL ,
gender CHAR(1) DEFAULT NULL ,
address VARCHAR(256) DEFAULT NULL
);
CREATE TABLE goods (
id INT AUTO_INCREMENT PRIMARY KEY,
goodsname VARCHAR(32),
price FLOAT(10,1),
memo TEXT ,
pic VARCHAR(200) ,
createtime DATETIME
);
CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,
userid INT ,
createtime DATETIME NOT NULL,
memo VARCHAR(100)
);
ALTER TABLE orders ADD CONSTRAINT fk_orders_userinfo_id FOREIGN KEY (userid)
REFERENCES userinfo (id);
CREATE TABLE orderdetail (
id INT AUTO_INCREMENT PRIMARY KEY,
orderid INT NOT NULL ,
goodsid INT NOT NULL ,
itemsnum INT DEFAULT NULL
);
ALTER TABLE orderdetail ADD CONSTRAINT fk_orderdetail_orders_id FOREIGN KEY
(orderid)
REFERENCES orders (id);
ALTER TABLE orderdetail ADD CONSTRAINT fk_orderdetail_goods_id FOREIGN KEY
(goodsid)
REFERENCES goods (id);
INSERT INTO userinfo(userName,userPass,birthday,gender,address)
VALUES('admin','admin','1980-10-10','男','陕西西安');
INSERT INTO userinfo(userName,userPass,birthday,gender,address)
VALUES('林冲','lichong','1982-11-10','男','河南开封');
INSERT INTO userinfo(userName,userPass,birthday,gender,address)
VALUES('扈三娘','husanniang','1981-03-10','女','山东聊城');
INSERT INTO userinfo(userName,userPass,birthday,gender,address)
VALUES('孙二娘','sunerniang','1979-03-10','女','山东曾头市');
INSERT INTO goods(goodsname,price,memo,pic,createtime)
VALUES('平谷大桃','33.6','产自河北','pinggudatao.jpg','2017-12-12 12:12:12');
INSERT INTO goods(goodsname,price,memo,pic,createtime)
VALUES('油桃','17.6','产自河北','youtao.jpg','2017-12-13 12:12:12');
INSERT INTO goods(goodsname,price,memo,pic,createtime)
VALUES('水蜜桃','39.6','产自河北','shuimitao.jpg','2017-12-14 12:12:12');
INSERT INTO goods(goodsname,price,memo,pic,createtime)
VALUES('蟠桃','33.6','产自河北','pantao.jpg','2017-12-11 12:12:12');
INSERT INTO goods(goodsname,price,memo,pic,createtime)
VALUES('毛桃','31.6','产自河北','maotao.jpg','2017-10-12 12:12:12');
INSERT INTO goods(goodsname,price,memo,pic,createtime)
VALUES('樱桃','43.6','产自河北','yingtao.jpg','2017-11-22 12:12:12');
INSERT INTO orders(userid,createtime,memo)
VALUES(2,'2017-09-21 16:26:51','要新鲜的');
INSERT INTO orderdetail(orderid,goodsid,itemsnum)VALUES(1,1,3);
INSERT INTO orderdetail(orderid,goodsid,itemsnum)VALUES(1,2,2);
INSERT INTO orders(userid,createtime,memo)
VALUES(2,'2017-09-22 16:26:50','和上次的一样');
INSERT INTO orderdetail(orderid,goodsid,itemsnum)VALUES(2,1,3);
INSERT INTO orderdetail(orderid,goodsid,itemsnum)VALUES(2,2,2);
整合Mybatis
新建模块,名称:springboot004
第一步:创建项目
第二步:在 pom.xml 中添加相关 jar 依赖
<!--MyBatis 整合 SpringBoot 的起步依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<!--MySQL 的驱动依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
提示:MySQL 的驱动依赖的版本由父工程管理
第三步:配置数据源
SpringBoot2.6.0版本默认使用Hikari数据源
#配置数据库的连接信息
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/eshop?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=
第四步:编写Model类
在com.ltw.model包中创建实体类
public class Goods {
private Integer id;
private String goodsname;
private Double price;
private String memo;
private String pic;
private Date createtime;
}
第四步:编写Controller
在com.ltw.controller包中创建控制器 请求/goods/list 响应JSON
@Controller
@RequestMapping("/goods")
public class GoodsController {
@Autowired
private IGoodsService goodsService;
@RequestMapping("/list")
public @ResponseBody Object list(){
List<Goods> list = goodsService.selectList();
return list;
}
public IGoodsService getGoodsService() {
return goodsService;
}
public void setGoodsService(IGoodsService goodsService) {
this.goodsService = goodsService;
}
}
第五步:编写Service接口和实现类
在com.ltw.service包中创建业务接口 Service接口
public interface IGoodsService {
List<Goods> selectList();
}
在com.ltw.service.impl包中创建业务实现 Service实现
@Service
public class GoodsService implements IGoodsService {
@Autowired
private IGoodsMapper goodsMapper;
@Override
public List<Goods> selectList() {
return goodsMapper.selectList();
}
public IGoodsMapper getGoodsMapper() {
return goodsMapper;
}
public void setGoodsMapper(IGoodsMapper goodsMapper) {
this.goodsMapper = goodsMapper;
}
}
第六步:编写Mapper接口
在com.ltw.mapper包中创建Mapper接口
@Mapper
public interface IGoodsMapper {
List<Goods> selectList();
}
@Mapper注解创建了Mapper对象,并交给spring容器管理
第七步:编写Mapper映射
在com.ltw.mapper包中创建Mapper映射配置文件
<?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.ltw.mapper.IGoodsMapper">
<resultMap id="goodsMap" type="com.ltw.model.Goods">
<id column="id" property="id" />
<result column="goodsname" property="goodsname" />
<result column="price" property="price"/>
<result column="memo" property="memo"/>
<result column="pic" property="pic"/>
<result column="createtime" property="createtime"/>
</resultMap>
<select id="selectList" resultMap="goodsMap">
select * from goods
</select>
</mapper>
第八步:配置Mapper映射文件编译目录
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
第九步:配置Mapper映射文件存储位置
#配置mapper映射文件存储位置
mybatis.mapper-locations=classpath:**/mapper/*.xml
**表示classpath目录及其所有的子目录
至此整合Mybatis完毕,完整的项目结构如下
第十步:启动测试
优化Mapper接口开发
@Mapper注解标注在Mapper接口上,实现了Mapper接口对象的创建 使用@MapperScan注解扫描包,可省去在每个Mapper接口上标注@Mapper
第一步:注释掉@Mapper注解
public interface IGoodsMapper {
List<Goods> selectList();
}
第二步:打开@MapperScan注解
@SpringBootApplication
@MapperScan(basePackages = "com.kaifamiao.mapper")
public class Springboot005Application {
public static void main(String[] args) {
SpringApplication.run(Springboot005Application.class, args);
}
}
整合数据源
SpringBoot 2.6.0默认使用HikariDataSource,我们可以通过测试类确认SpringBoot使用的数据源。
测试默认数据源
在测试类中将Spring容器中的数据源对象注入给测试类的属性,输出类名。
@SpringBootTest
class Springboot005ApplicationTests {
@Autowired
DataSource dataSource;
@Test
void contextLoads() {
System.out.println(dataSource.getClass());
}
}
运行测试类,观察控制器输出结果如下: 证明默认使用的是Hikari数据源。 Springboot默认支持4种数据源类型,分别是:
- org.apache.tomcat.jdbc.pool.DataSource
- com.zaxxer.hikari.HikariDataSource
- org.apache.commons.dbcp.BasicDataSource
- org.apache.commons.dbcp2.BasicDataSource
对于这4种数据源,当 classpath 下有相应的类存在时,Springboot 会通过自动配置为其生成DataSourceBean,DataSourceBean默认只会生成一个,四种数据源类型的生效先后顺序如下:Tomcat–> Hikari --> Dbcp --> Dbcp2 。
切换成其他数据源
如果想更换成其他数据源,需要两个步骤,例如更换成Druid数据源。 第一步:导入数据源jar包
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
第二步:在application.properties中配置数据源
#数据源基本配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/eshop?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=
#切换成Druid数据源
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
测试数据源切换是否成功 重新运行测试类
@SpringBootTest
class Springboot005ApplicationTests {
@Autowired
DataSource dataSource;
@Test
void contextLoads() {
System.out.println(dataSource.getClass());
}
}
运行测试类,观察控制器输出结果如下: class com.alibaba.druid.pool.DruidDataSource 证明数据源已经切换成Druid。
数据源的基本配置
数据源的基本配置包括了 spring.datasource.driver-class-name spring.datasource.url spring.datasource.username spring.datasource.password 以上四个数据源基本配置对于每一种数据源都是通用的。
数据源的其他信息
虽然数据源基本配置对于每一种数据源都是通用的,但是不同数据源连接池参数配置不是通用的。
测试连接池默认参数
现在数据源已经切换到Druid,Druid负责管理连接池,而连接池参数的设置是否合理在极大程度上影响程序运行的性能。那么此时Druid连接池参数是怎么设置的呢? 在调试模式下运行下面的测试,在控制台中观察默认情况下Druid的连接池参数默认设置。
@SpringBootTest
class Springboot005ApplicationTests {
@Autowired
DataSource dataSource;
@Test
void contextLoads() throws SQLException {
System.out.println(dataSource.getClass());
}
}
发现连接池初始化大小默认为0。如何设置连接池运行参数呢。 下面介绍如何配置德鲁伊数据源的连接池参数配置。
连接池其他参数设置
第一步:配置连接池参数 首先将德鲁伊连接池配置参数输入到application.properties配置文件中
#数据源连接池的参数配置
spring.datasource.initialSize: 5
spring.datasource.minIdle: 5
spring.datasource.maxActive: 20
spring.datasource.maxWait: 60000
第二步:自定义Druid数据源对象,交给spring容器管理 使用Java-based方式定义Druid数据源的bean对象,交给spring容器管理
package com.ltw.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class DruidConfig {
@Bean
spring.datasource的属性注入给DruidDataSource的bean对象
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druid(){
return new DruidDataSource();
}
}
解释: @ConfigurationProperties注解用于读取application.properties中的配置信息 参数prefix = "spring.datasource"表示读取以"spring.datasource"开头的配置信息,并按照"spring.datasource"后面的名称与Druid连接池参数名称相同注入属性值。 第三步:测试连接池其他参数设置 在调试模式下运行下面的测试,在控制台中观察默认情况下Druid的连接池参数默认设置。
@SpringBootTest
class Springboot005ApplicationTests {
@Autowired
DataSource dataSource;
@Test
void contextLoads() throws SQLException {
System.out.println(dataSource.getClass());
}
}
发现连接池初始化大小默认为5。application.perperties中连接池参数设置已经生效。
整合事务支持
Spring Boot 使用事务非常简单,底层依然采用的是 Spring 本身提供的事务管理
在入口类中使用注解 @EnableTransactionManagement 开启事务支持 在访问数据库的 Service 方法上添加注解 @Transactional 即可
事务执行的三种情况: 当方法内部抛出RuntimeException时事务回滚 当方法内部抛出Exception时事务提交 当方法内部没有抛出异常时事务提交
提示:事务是在业务层实现的
以添加订单为例,测试事务 一个订单中购买两件商品 向orders表中添加订单 向orderdetail表中添加订单中购买的商品
第一步:在Mapper接口中定义添加商品方法
public interface IOrdersMapper {
int insert(Orders record);
}
public interface IOrderdetailMapper {
int insert(Orderdetail record);
}
第二步:定义Mapper映射
<insert id="insert" parameterType="com.kaifamiao.model.Orders">
<selectKey order="AFTER" keyProperty="id" resultType="java.lang.Integer">
SELECT LAST_INSERT_ID()
</selectKey>
INSERT INTO orders(userid,createtime,memo)
VALUES(#{userid},#{createtime},#{memo})
</insert>
<insert id="insert" parameterType="com.kaifamiao.model.Orderdetail" >
insert into orderdetail (id, orderid, goodsid, itemsnum)
values (#{id}, #{orderid}, #{goodsid}, #{itemsnum)
</insert>
第三步:在Service接口中定义添加商品方法
public interface IOrderService {
public void insert(Orders orders, Orderdetail orderdetail[]) throws
Exception;
}
注意:方法参数中包含了订单和订单中购买的商品
第四步:在Service实现类中实现添加商品方法
在insert方法上标注@Transactional注解,会在调用方法前开启事务,在方法调用结束后提交事务,在方法跑车RuntimeException时回滚事务。
@Transactional
public void insert(Orders orders, Orderdetail[] orderdetail) throws
Exception {
ordersMapper.insert(orders);
if(orderdetail!=null && orderdetail.length>0) {
for (int i = 0; i < orderdetail.length; i++) {
orderdetail[i].setOrderid(orders.getId());
orderdetailMapper.insert(orderdetail[i]);
}
}
}
第五步:编写控制器类
@RequestMapping("/insert")
public @ResponseBody String addOrder() throws Exception {
Orders orders = new Orders();
orders.setUserid(1);
orders.setMemo("发票一起寄出");
orders.setCreatetime(new Date());
Orderdetail orderdetail1 =new Orderdetail();
orderdetail1.setGoodsid(1);
orderdetail1.setItemsnum(3);
Orderdetail orderdetail2 =new Orderdetail();
orderdetail2.setGoodsid(1);
orderdetail2.setItemsnum(3);
Orderdetail orderdetails[] =new Orderdetail[]{
orderdetail1,
orderdetail2
};
orderService.insert(orders,orderdetails);
return "已添加订单";
}
第六步:在启动类上开启事务
@SpringBootApplication
@MapperScan(basePackages = "com.kaifamiao.mapper")
@EnableTransactionManagement
public class Springboot005Application {
public static void main(String[] args) {
SpringApplication.run(Springboot005Application.class, args);
}
}
第七步:启动测试事务
测试业务方法正常执行
业务方法代码如下:程序正常运行,没有抛出异常,下单后提交事务,下单成功。
@Transactional
public void insert(Orders orders, Orderdetail[] orderdetail) throws
Exception {
ordersMapper.insert(orders);
if(orderdetail!=null && orderdetail.length>0) {
for (int i = 0; i < orderdetail.length; i++) {
orderdetail[i].setOrderid(orders.getId());
orderdetailMapper.insert(orderdetail[i]);
}
}
}
运行后查看数据库 结果显示:订单添加正常
测试方法抛出RuntimeException
业务方法代码中抛出运行时异常,事务回滚,下单失败。
@Transactional
public void insert(Orders orders, Orderdetail[] orderdetail) throws
Exception {
ordersMapper.insert(orders);
if(orderdetail!=null && orderdetail.length>0) {
for (int i = 0; i < orderdetail.length; i++) {
orderdetail[i].setOrderid(orders.getId());
orderdetailMapper.insert(orderdetail[i]);
throw new RuntimeException("事务中发生运行时异常");
}
}
}
运行后抛出异常如下: 由于抛出运行时异常,导致事务回滚,查看数据库,orders表和orderdetail表都没有新增记录,证明事务已经回滚。
测试方法抛出Exception
业务方法代码中抛出编译时异常,事务提交,但是下单失败,订单表中新增的记录与订单明细表中新增的记录不一致。
@Transactional
public void insert(Orders orders, Orderdetail[] orderdetail) throws
Exception {
ordersMapper.insert(orders);
if(orderdetail!=null && orderdetail.length>0) {
for (int i = 0; i < orderdetail.length; i++) {
orderdetail[i].setOrderid(orders.getId());
orderdetailMapper.insert(orderdetail[i]);
throw new Exception("事务中发生编译时异常");
}
}
}
运行后抛出异常如下: 由于抛出编译时异常,事务仍然提交,查看数据库,orders表中新增一条订单记录,orderdetail表中新增一条订单明细记录,丢失了一条订单明细记录,证明抛出编译时异常事务仍然提交,导致事务控制失败。
|