环境
- Windows10
- JDK1.8
- MySql5.5
工具
- Maven3.5
- IDEA2017
- Navicat(数据库连接工具)
准备工作
步骤
1、新建Maven webapp项目
建好基本目录,按住 Ctrl+Alt+Shift+s配置目录,如下图
新建几个包,如下图:
包名 | 名称 | 作用 |
---|---|---|
dao | 数据访问层(接口 | 与数据操作有关的都放在这里 |
entity | 实体类 | 一般与数据库的表相对应,封装dao层取出来的数据为一个对象,也就是我们常说的pojo,一般只在dao层与service层之间传输 |
dto | 数据传输层 | 用于service层与web层之间传输,相当于vo |
service | 业务逻辑(接口) | 写我们的业务逻辑,在设计业务接口时候应该站在“使用者”的角度。 |
serviceImpl | 业务逻辑(实现) | 实现我们业务接口,一般事务控制是写在这里,没什么好说的。 |
controller | 控制器 | springmvc就是在这里发挥作用的 |
2、配置文件
配置完成的目录如下:
-
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.messchx</groupId> <artifactId>ssmdemo</artifactId> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <name>ssmdemo Maven Webapp</name> <url>http://maven.apache.org</url> <build> <finalName>ssmdemo</finalName> <plugins> <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> <version>6.1.7</version> <configuration> <connectors> <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector"> <port>8888</port> <maxIdleTime>30000</maxIdleTime> </connector> </connectors> <webAppSourceDirectory>${project.build.directory}/${pom.artifactId}-${pom.version} </webAppSourceDirectory> <contextPath>/</contextPath> </configuration> </plugin> <!--mybatis逆向工程插件--> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.2</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <!-- 设置JDK版本 --> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> <!--若不配置,可能会发送打包不全--> <resources> <resource> <!--需要打包的目录--> <directory>src/main/java</directory> <!--目录中的文件类型--> <includes> <include>**/*.xml</include> <include>**/*.properties</include> </includes> </resource> <resource> <!--需要打包的目录--> <directory>src/main/resources</directory> <!--目录中的文件类型--> <includes> <include>**/*.xml</include> <include>**/*.properties</include> </includes> </resource> </resources> </build> <properties> <!-- 设置项目编码编码 --> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <!-- spring版本号 --> <spring.version>4.3.5.RELEASE</spring.version> <!-- mybatis版本号 --> <mybatis.version>3.4.1</mybatis.version> </properties> <dependencies> <!-- java ee --> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> </dependency> <!-- 单元测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <!-- 实现slf4j接口并整合 --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.2</version> </dependency> <!-- JSON --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.8.7</version> </dependency> <!-- 数据库 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.41</version> <scope>runtime</scope> </dependency> <!-- 数据库连接池 --> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.2</version> </dependency> <!-- MyBatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>${mybatis.version}</version> </dependency> <!-- mybatis/spring整合包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.1</version> </dependency> <!-- Spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> </dependency> </dependencies> </project>
-
spring配置文件,主要是dao、service、controller
-
spring-dao.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 配置整合mybatis过程 --> <!-- 1.配置数据库相关参数properties的属性:${url} --> <context:property-placeholder location="classpath:jdbc.properties" /> <!-- 2.数据库连接池 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!-- 配置连接池属性 --> <property name="driverClass" value="${jdbc.driver}" /> <property name="jdbcUrl" value="${jdbc.url}" /> <property name="user" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <!-- c3p0连接池的私有属性 --> <property name="maxPoolSize" value="30" /> <property name="minPoolSize" value="10" /> <!-- 关闭连接后不自动commit --> <property name="autoCommitOnClose" value="false" /> <!-- 获取连接超时时间 --> <property name="checkoutTimeout" value="10000" /> <!-- 当获取连接失败重试次数 --> <property name="acquireRetryAttempts" value="2" /> </bean> <!-- 3.配置SqlSessionFactory对象 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 注入数据库连接池 --> <property name="dataSource" ref="dataSource" /> <!-- 配置MyBaties全局配置文件:mybatis-config.xml --> <property name="configLocation" value="classpath:mybatis-config.xml" /> <!-- 扫描entity包 使用别名 --> <property name="typeAliasesPackage" value="com.soecode.lyf.entity" /> <!-- 扫描sql配置文件:mapper需要的xml文件 --> <property name="mapperLocations" value="classpath:mapper/*.xml" /> </bean> <!-- 4.配置扫描Dao接口包,动态实现Dao接口,注入到spring容器中 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 注入sqlSessionFactory --> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> <!-- 给出需要扫描Dao接口包 --> <property name="basePackage" value="com.soecode.lyf.dao" /> </bean> </beans>
jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf8 jdbc.username=root jdbc.password=root
提示:配置文件中的jdbc.username,如果写成username,可能会与系统环境中的username变量冲突,所以到时候真正连接数据库的时候,用户名就被替换成系统中的用户名(有得可能是administrator),那肯定是连接不成功的
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> <!-- 使用jdbc的getGeneratedKeys获取数据库自增主键值 --> <setting name="useGeneratedKeys" value="true" /> <!-- 使用列别名替换列名 默认:true --> <setting name="useColumnLabel" value="true" /> <!-- 开启驼峰命名转换:Table{create_time} -> Entity{createTime} --> <setting name="mapUnderscoreToCamelCase" value="true" /> </settings> </configuration>
-
spring-service.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 扫描service包下所有使用注解的类型 --> <context:component-scan base-package="com.messchx.ssm.service" /> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入数据库连接池 --> <property name="dataSource" ref="dataSource" /> </bean> <!-- 配置基于注解的声明式事务 --> <tx:annotation-driven transaction-manager="transactionManager" /> </beans>
-
spring-controller.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd"> <!-- 配置SpringMVC --> <!-- 1.开启SpringMVC注解模式 --> <!-- 简化配置: (1)自动注册DefaultAnootationHandlerMapping,AnotationMethodHandlerAdapter (2)提供一些列:数据绑定,数字和日期的format @NumberFormat, @DateTimeFormat, xml,json默认读写支持 --> <mvc:annotation-driven /> <!-- 2.静态资源默认servlet配置 (1)加入对静态资源的处理:js,gif,png (2)允许使用"/"做整体映射 --> <mvc:default-servlet-handler/> <!-- 3.配置jsp 显示ViewResolver --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> </bean> <!-- 4.扫描web相关的bean --> <context:component-scan base-package="com.messchx.ssm.controller" /> </beans>
-
-
web.xml
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1" metadata-complete="true"> <!-- 如果是用mvn命令生成的xml,需要修改servlet版本为3.1 --> <!-- 配置DispatcherServlet --> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 配置springMVC需要加载的配置文件 spring-dao.xml,spring-service.xml,spring-web.xml Mybatis - > spring -> springmvc --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring-*.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <!-- 默认匹配所有的请求 --> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
-
logback.xml(日志)
<?xml version="1.0" encoding="UTF-8"?> <configuration debug="true"> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <!-- encoders are by default assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder --> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <root level="debug"> <appender-ref ref="STDOUT" /> </root> </configuration>
-
generatorConfig.xml,mybatis逆向工程,生成dao(即mapper)和model(即po),这里先不用
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <!--导入属性配置--> <properties resource="jdbc.properties"></properties> <!--指定特定数据库的jdbc驱动jar包的位置--> <classPathEntry location="${jdbc.driverLocation}"/> <context id="default" targetRuntime="MyBatis3"> <!-- optional,旨在创建class时,对注释进行控制 --> <commentGenerator> <property name="suppressDate" value="true"/> <property name="suppressAllComments" value="true"/> </commentGenerator> <!--jdbc的数据库连接 --> <jdbcConnection driverClass="${jdbc.driverClasss}" connectionURL="${jdbc.url}" userId="${jdbc.username}" password="${jdbc.password}"> </jdbcConnection> <!-- 非必需,类型处理器,在数据库类型和java类型之间的转换控制--> <javaTypeResolver> <property name="forceBigDecimals" value="false"/> </javaTypeResolver> <!-- 实体类 targetPackage 指定生成的model生成所在的包名 targetProject 指定在该项目下所在的路径 --> <javaModelGenerator targetPackage="com.messchx.ssm.entity" targetProject="src/main/java"> <!-- 是否允许子包,即targetPackage.schemaName.tableName --> <property name="enableSubPackages" value="false"/> <!-- 是否对model添加 构造函数 --> <property name="constructorBased" value="true"/> <!-- 是否对类CHAR类型的列的数据进行trim操作 --> <property name="trimStrings" value="true"/> <!-- 建立的Model对象是否 不可改变 即生成的Model对象不会有 setter方法,只有构造方法 --> <property name="immutable" value="false"/> </javaModelGenerator> <!--Mapper映射文件生成所在的目录 为每一个数据库的表生成对应的SqlMap文件 --> <sqlMapGenerator targetPackage="com.messchx.ssm.mapper" targetProject="src/main/java"> <property name="enableSubPackages" value="false"/> </sqlMapGenerator> <!-- 客户端代码,生成易于使用的针对Model对象和XML配置文件 的代码 type="ANNOTATEDMAPPER",生成Java Model 和基于注解的Mapper对象 type="MIXEDMAPPER",生成基于注解的Java Model 和相应的Mapper对象 type="XMLMAPPER",生成SQLMap XML文件和独立的Mapper接口 --> <javaClientGenerator targetPackage="com.messchx.ssm.dao" targetProject="src/main/java" type="XMLMAPPER"> <property name="enableSubPackages" value="true"/> </javaClientGenerator> <!--数据库中的表--> <table tableName="user"></table> </context> </generatorConfiguration>
到这里基本的配置都完了,剩下的就是写代码。
3、代码,应用实例(图书管理系统)
mysql数据文件:
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for appointment
-- ----------------------------
DROP TABLE IF EXISTS `appointment`;
CREATE TABLE `appointment` (
`book_id` bigint(20) NOT NULL COMMENT '图书ID',
`student_id` bigint(20) NOT NULL COMMENT '学号',
`appoint_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '预约时间',
PRIMARY KEY (`book_id`,`student_id`),
KEY `idx_appoint_time` (`appoint_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='预约图书表';
-- ----------------------------
-- Records of appointment
-- ----------------------------
INSERT INTO `appointment` VALUES ('1000', '12345678910', '2018-01-16 15:44:36');
INSERT INTO `appointment` VALUES ('1001', '12345678910', '2018-01-16 15:58:02');
-- ----------------------------
-- Table structure for book
-- ----------------------------
DROP TABLE IF EXISTS `book`;
CREATE TABLE `book` (
`book_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '图书ID',
`name` varchar(100) NOT NULL COMMENT '图书名称',
`number` int(11) NOT NULL COMMENT '馆藏数量',
PRIMARY KEY (`book_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1004 DEFAULT CHARSET=utf8 COMMENT='图书表';
-- ----------------------------
-- Records of book
-- ----------------------------
INSERT INTO `book` VALUES ('1000', 'Java程序设计', '9');
INSERT INTO `book` VALUES ('1001', '数据结构', '9');
INSERT INTO `book` VALUES ('1002', '设计模式', '10');
INSERT INTO `book` VALUES ('1003', '编译原理', '10');
3.1 dao层
在entity包中添加图书实体Book.java和预约图书实体Appointment.java
Book.java
public class Book {
private long bookId;// 图书ID
private String name;// 图书名称
private int number;// 馆藏数量
// 省略构造方法,getter和setter方法,toString方法
}
Appointment.java
/**预约图书实体*/
public class Appointment {
private long bookId;// 图书ID
private long studentId;// 学号
private Date appointTime;// 预约时间
// 多对一的复合属性
private Book book;// 图书实体
// 省略构造方法,getter和setter方法,toString方法
}
在dao包新建接口BookDao.java和Appointment.java
BookDao.java
public interface BookDao {
/**
* 通过ID查询单本图书
*
* @param id
* @return
*/
Book queryById(long id);
/**
* 查询所有图书
*
* @param offset 查询起始位置
* @param limit 查询条数
* @return
*/
List<Book> queryAll(@Param("offset") int offset, @Param("limit") int limit);
/**
* 减少馆藏数量
*
* @param bookId
* @return 如果影响行数等于>1,表示更新的记录行数
*/
int reduceNumber(long bookId);
}
AppointmentDao.java
public interface AppointmentDao {
/**
* 插入预约图书记录
*
* @param bookId
* @param studentId
* @return 插入的行数
*/
int insertAppointment(@Param("bookId") long bookId, @Param("studentId") long studentId);
/**
* 通过主键查询预约图书记录,并且携带图书实体
*
* @param bookId
* @param studentId
* @return
*/
Appointment queryByKeyWithBook(@Param("bookId") long bookId, @Param("studentId") long studentId);
}
提示:这里为什么要给方法的参数添加@Param注解呢?是因为该方法有两个或以上的参数,一定要加,不然mybatis识别不了。上面的BookDao接口的queryById方法和reduceNumber方法只有一个参数book_id,所以可以不用加 @Param注解,当然加了也无所谓~
我们不需要实现dao接口, mybatis会动态实现,但是我们需要编写相应的mapper。在mapper目录里新建两个文件BookDao.xml和ppointmentDao.xml,分别对应上面两个dao接口。
BookDao.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">
<mapper namespace="com.messchx.ssm.dao.BookDao">
<!-- 目的:为dao接口方法提供sql语句配置 -->
<select id="queryById" resultType="Book" parameterType="long">
<!-- 具体的sql -->
SELECT
book_id,
name,
number
FROM
book
WHERE
book_id = #{bookId}
</select>
<select id="queryAll" resultType="Book">
SELECT
book_id,
name,
number
FROM
book
ORDER BY
book_id
LIMIT #{offset}, #{limit}
</select>
<update id="reduceNumber">
UPDATE book
SET number = number - 1
WHERE
book_id = #{bookId}
AND number > 0
</update>
</mapper>
AppointmentDao.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">
<mapper namespace="com.messchx.ssm.dao.AppointmentDao">
<insert id="insertAppointment">
<!-- ignore 主键冲突,报错 -->
INSERT ignore INTO appointment (book_id, student_id)
VALUES (#{bookId}, #{studentId})
</insert>
<select id="queryByKeyWithBook" resultType="Appointment">
<!-- 如何告诉MyBatis把结果映射到Appointment同时映射book属性 -->
<!-- 可以自由控制SQL -->
SELECT
a.book_id,
a.student_id,
a.appoint_time,
b.book_id "book.book_id",
b.`name` "book.name",
b.number "book.number"
FROM
appointment a
INNER JOIN book b ON a.book_id = b.book_id
WHERE
a.book_id = #{bookId}
AND a.student_id = #{studentId}
</select>
</mapper>
mapper总结:namespace是该xml对应的接口全名,select和update中的id对应方法名,resultType是返回值类型,parameterType是参数类型(这个其实可选),最后#{...}中填写的是方法的参数,还有一个小技巧要交给大家,就是在返回Appointment对象包含了一个属性名为book的Book对象,那么可以使用"book.属性名"的方式来取值,看上面queryByKeyWithBook方法的sql。
测试:在测试分支建立相应的包,测试前需要让程序读入spring-dao和mybatis等配置文件,所以我这里就抽离出来一个BaseTest类,只要是测试方法就继承它即可。
BaseTest.java
/**
* 配置spring和junit整合,junit启动时加载springIOC容器 spring-test,junit
*/
@RunWith(SpringJUnit4ClassRunner.class)
// 告诉junit spring配置文件
@ContextConfiguration({ "classpath:spring/spring-dao.xml", "classpath:spring/spring-service.xml" })
public class BaseTest {
}
新建BookDaoTest.java和AppointmentDaoTest.java两个dao测试文件
BookDaoTest.java
public class BookDaoTest extends BaseTest {
@Autowired
private BookDao bookDao;
@Test
public void testQueryById() throws Exception {
long bookId = 1000;
Book book = bookDao.queryById(bookId);
System.out.println(book);
}
@Test
public void testQueryAll() throws Exception {
List<Book> books = bookDao.queryAll(0, 4);
for (Book book : books) {
System.out.println(book);
}
}
@Test
public void testReduceNumber() throws Exception {
long bookId = 1000;
int update = bookDao.reduceNumber(bookId);
System.out.println("update=" + update);
}
}
BookDaoTest测试结果,testQueryById
testQueryAll
testReduceNumber
AppointmentDaoTest.java
public class AppointmentDaoTest extends BaseTest{
@Autowired
private AppointmentDao appointmentDao;
@Test
public void testInsertAppointment() throws Exception {
long bookId = 1000;
long studentId = 12345678910L;
int insert = appointmentDao.insertAppointment(bookId, studentId);
System.out.println("insert=" + insert);
}
@Test
public void testQueryByKeyWithBook() throws Exception {
long bookId = 1000;
long studentId = 12345678910L;
Appointment appointment = appointmentDao.queryByKeyWithBook(bookId, studentId);
System.out.println(appointment);
System.out.println(appointment.getBook());
}
}
AppointmentDaoTest测试结果,testInsertAppointment
testQueryByKeyWithBook
3.2 servic层
我们先定义几个预约图书操作返回码的数据字典,也就是我们要返回给客户端的信息。我们这类使用枚举类
返回码 | 说明 |
---|---|
1 | 预约成功 |
0 | 库存不足 |
-1 | 重复预约 |
-2 | 系统异常 |
AppointStateEnum.java
package com.messchx.ssm.enums;
/**
* 使用枚举表述常量数据字典
*/
public enum AppointStateEnum {
SUCCESS(1, "预约成功"), NO_NUMBER(0, "库存不足"), REPEAT_APPOINT(-1, "重复预约"), INNER_ERROR(-2, "系统异常");
private int state;
private String stateInfo;
private AppointStateEnum(int state, String stateInfo) {
this.state = state;
this.stateInfo = stateInfo;
}
public int getState() {
return state;
}
public String getStateInfo() {
return stateInfo;
}
public static AppointStateEnum stateOf(int index) {
for (AppointStateEnum state : values()) {
if (state.getState() == index) {
return state;
}
}
return null;
}
}
在dto包下新建AppointExecution.java用来存储我们执行预约操作的返回结果。
AppointExecution.java
/**
* 封装预约执行后结果
*/
public class AppointExecution {
// 图书ID
private long bookId;
// 秒杀预约结果状态
private int state;
// 状态标识
private String stateInfo;
// 预约成功对象
private Appointment appointment;
public AppointExecution() {
}
// 预约失败的构造器
public AppointExecution(long bookId, AppointStateEnum stateEnum) {
this.bookId = bookId;
this.state = stateEnum.getState();
this.stateInfo = stateEnum.getStateInfo();
}
// 预约成功的构造器
public AppointExecution(long bookId, AppointStateEnum stateEnum, Appointment appointment) {
this.bookId = bookId;
this.state = stateEnum.getState();
this.stateInfo = stateEnum.getStateInfo();
this.appointment = appointment;
}
// 省略getter和setter方法,toString方法
}
在exception包下新建三个文件NoNumberException.java RepeatAppointException.java AppointException.java预约业务异常类(都需要继承RuntimeException),分别是无库存异常、重复预约异常、预约未知错误异常,用于业务层非成功情况下的返回(即成功返回结果,失败抛出异常)。
NoNumberException.java
/**
* 库存不足异常
*/
public class NoNumberException extends RuntimeException {
public NoNumberException(String message) {
super(message);
}
public NoNumberException(String message, Throwable cause) {
super(message, cause);
}
}
RepeatAppointException.java
/**
* 重复预约异常
*/
public class RepeatAppointException extends RuntimeException {
public RepeatAppointException(String message) {
super(message);
}
public RepeatAppointException(String message, Throwable cause) {
super(message, cause);
}
}
AppointException.java
/**
* 预约业务异常
*/
public class AppointException extends RuntimeException {
public AppointException(String message) {
super(message);
}
public AppointException(String message, Throwable cause) {
super(message, cause);
}
}
在service包下新建BookService.java图书业务接口
BookService.java
/**
* 业务接口:站在"使用者"角度设计接口 三个方面:方法定义粒度,参数,返回类型(return 类型/异常)
*/
public interface BookService {
/**
* 查询一本图书
*
* @param bookId
* @return
*/
Book getById(long bookId);
/**
* 查询所有图书
*
* @return
*/
List<Book> getList();
/**
* 预约图书
*
* @param bookId
* @param studentId
* @return
*/
AppointExecution appoint(long bookId, long studentId);
}
在service.impl包下新建BookServiceImpl.java使用BookService接口,并实现里面的方法。
BookServiceImpl
@Service
public class BookServiceImpl implements BookService {
private Logger logger = LoggerFactory.getLogger(this.getClass());
// 注入Service依赖
@Autowired
private BookDao bookDao;
@Autowired
private AppointmentDao appointmentDao;
@Override
public Book getById(long bookId) {
return bookDao.queryById(bookId);
}
@Override
public List<Book> getList() {
return bookDao.queryAll(0, 1000);
}
@Override
@Transactional
/**
* 使用注解控制事务方法的优点: 1.开发团队达成一致约定,明确标注事务方法的编程风格
* 2.保证事务方法的执行时间尽可能短,不要穿插其他网络操作,RPC/HTTP请求或者剥离到事务方法外部
* 3.不是所有的方法都需要事务,如只有一条修改操作,只读操作不需要事务控制
*/
public AppointExecution appoint(long bookId, long studentId) {
try {
// 减库存
int update = bookDao.reduceNumber(bookId);
if (update <= 0) {// 库存不足
//return new AppointExecution(bookId, AppointStateEnum.NO_NUMBER);//错误写法
throw new NoNumberException("no number");
} else {
// 执行预约操作
int insert = appointmentDao.insertAppointment(bookId, studentId);
if (insert <= 0) {// 重复预约
//return new AppointExecution(bookId, AppointStateEnum.REPEAT_APPOINT);//错误写法
throw new RepeatAppointException("repeat appoint");
} else {// 预约成功
Appointment appointment = appointmentDao.queryByKeyWithBook(bookId, studentId);
return new AppointExecution(bookId, AppointStateEnum.SUCCESS, appointment);
}
}
// 要先于catch Exception异常前先catch住再抛出,不然自定义的异常也会被转换为AppointException,导致控制层无法具体识别是哪个异常
} catch (NoNumberException e1) {
throw e1;
} catch (RepeatAppointException e2) {
throw e2;
} catch (Exception e) {
logger.error(e.getMessage(), e);
// 所有编译期异常转换为运行期异常
//return new AppointExecution(bookId, AppointStateEnum.INNER_ERROR);//错误写法
throw new AppointException("appoint inner error:" + e.getMessage());
}
}
}
测试一下业务代码
BookServiceImplTest.java
public class BookServiceImplTest extends BaseTest {
@Autowired
private BookService bookService;
@Test
public void testAppoint() throws Exception {
long bookId = 1001;
long studentId = 12345678910L;
AppointExecution execution = bookService.appoint(bookId, studentId);
System.out.println(execution);
}
}
BookServiceImplTest测试结果
首次执行是“预约成功”,如果再次执行的话,应该会出现“重复预约”
在dto包里新建一个封装json返回结果的类Result.java,设计成泛型
Result.java
/**
* 封装json对象,所有返回结果都使用它
*/
public class Result<T> {
private boolean success;// 是否成功标志
private T data;// 成功时返回的数据
private String error;// 错误信息
public Result() {
}
// 成功时的构造器
public Result(boolean success, T data) {
this.success = success;
this.data = data;
}
// 错误时的构造器
public Result(boolean success, String error) {
this.success = success;
this.error = error;
}
// 省略getter和setter方法
}
3.3 controller层
BookController.java
@Controller
@RequestMapping("/book") // url:/模块/资源/{id}/细分 /seckill/list
public class BookController {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private BookService bookService;
@RequestMapping(value = "/list", method = RequestMethod.GET)
private String list(Model model) {
List<Book> list = bookService.getList();
model.addAttribute("list", list);
// list.jsp + model = ModelAndView
return "list";// WEB-INF/jsp/"list".jsp
}
@RequestMapping(value = "/{bookId}/detail", method = RequestMethod.GET)
private String detail(@PathVariable("bookId") Long bookId, Model model) {
if (bookId == null) {
return "redirect:/book/list";
}
Book book = bookService.getById(bookId);
if (book == null) {
return "forward:/book/list";
}
model.addAttribute("book", book);
return "detail";
}
//ajax json
@RequestMapping(value = "/{bookId}/appoint", method = RequestMethod.POST, produces = {
"application/json; charset=utf-8" })
@ResponseBody
private Result<AppointExecution> appoint(@PathVariable("bookId") Long bookId, @RequestParam("studentId") Long studentId) {
if (studentId == null || studentId.equals("")) {
return new Result<>(false, "学号不能为空");
}
//AppointExecution execution = bookService.appoint(bookId, studentId);//错误写法,不能统一返回,要处理异常(失败)情况
AppointExecution execution = null;
try {
execution = bookService.appoint(bookId, studentId);
} catch (NoNumberException e1) {
execution = new AppointExecution(bookId, AppointStateEnum.NO_NUMBER);
} catch (RepeatAppointException e2) {
execution = new AppointExecution(bookId, AppointStateEnum.REPEAT_APPOINT);
} catch (Exception e) {
execution = new AppointExecution(bookId, AppointStateEnum.INNER_ERROR);
}
return new Result<AppointExecution>(true, execution);
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。