手把手教你整合最优雅SSM框架:SpringMVC + Spring + MyBatis(1)

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Golang全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注go)
img

正文

http://github.com/liyifeng1994/ssm

junit junit 4.11 ch.qos.logback logback-classic 1.1.1 mysql mysql-connector-java 5.1.37 runtime c3p0 c3p0 0.9.1.2 org.mybatis mybatis 3.3.0 org.mybatis mybatis-spring 1.2.3 taglibs standard 1.1.2 jstl jstl 1.2 com.fasterxml.jackson.core jackson-databind 2.5.4 javax.servlet javax.servlet-api 3.1.0 org.springframework spring-core 4.1.7.RELEASE org.springframework spring-beans 4.1.7.RELEASE org.springframework spring-context 4.1.7.RELEASE org.springframework spring-jdbc 4.1.7.RELEASE org.springframework spring-tx 4.1.7.RELEASE org.springframework spring-web 4.1.7.RELEASE org.springframework spring-webmvc 4.1.7.RELEASE org.springframework spring-test 4.1.7.RELEASE redis.clients jedis 2.7.3 com.dyuproject.protostuff protostuff-core 1.0.8 com.dyuproject.protostuff protostuff-runtime 1.0.8 commons-collections commons-collections 3.2 ssm

下面真的要开始进行编码工作了,坚持到这里辛苦大家了~

第一步:我们先在spring文件夹里新建spring-dao.xml文件,因为spring的配置太多,我们这里分三层,分别是dao service web。

  1. 读入数据库连接相关参数(可选)
  2. 配置数据连接池
  3. 配置连接属性,可以不读配置项文件直接在这里写死
  4. 配置c3p0,只配了几个常用的
  5. 配置SqlSessionFactory对象(mybatis)
  6. 扫描dao层接口,动态实现dao接口,也就是说不需要daoImpl,sql和参数都写在xml文件上

spring-dao.xml

<?xml version="1.0" encoding="UTF-8"?>

<context:property-placeholder location=“classpath:jdbc.properties” />

因为数据库配置相关参数是读取配置文件,所以在resources文件夹里新建一个jdbc.properties文件,存放我们4个最常见的数据库连接属性,这是我本地的,大家记得修改呀~还有喜欢传到github上“大头虾们”记得删掉密码,不然别人就很容易得到你服务器的数据库配置信息,然后干一些羞羞的事情,你懂的!!

jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3307/ssm?useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=

友情提示:配置文件中的jdbc.username,如果写成username,可能会与系统环境中的username变量冲突,所以到时候真正连接数据库的时候,用户名就被替换成系统中的用户名(有得可能是administrator),那肯定是连接不成功的,这里有个小坑,我被坑了一晚上!!

因为这里用到了mybatis,所以需要配置mybatis核心文件,在recources文件夹里新建mybatis-config.xml文件。

  1. 使用自增主键
  2. 使用列别名
  3. 开启驼峰命名转换 create_time -> createTime

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>

第二步:刚弄好dao层,接下来到service层了。在spring文件夹里新建spring-service.xml文件。

  1. 扫描service包所有注解 @Service
  2. 配置事务管理器,把事务管理交由spring来完成
  3. 配置基于注解的声明式事务,可以直接在方法上@Transaction

spring-service.xml

<?xml version="1.0" encoding="UTF-8"?>

<context:component-scan base-package=“com.soecode.lyf.service” />

<tx:annotation-driven transaction-manager=“transactionManager” />


第三步:配置web层,在spring文件夹里新建spring-web.xml文件。

  1. 开启SpringMVC注解模式,可以使用@RequestMapping,@PathVariable,@ResponseBody等
  2. 对静态资源处理,如js,css,jpg等
  3. 配置jsp 显示ViewResolver,例如在controller中某个方法返回一个string类型的"login",实际上会返回"/WEB-INF/login.jsp"
  4. 扫描web层 @Controller

spring-web.xml

<?xml version="1.0" encoding="UTF-8"?>

<mvc:annotation-driven />

mvc:default-servlet-handler/

<context:component-scan base-package=“com.soecode.lyf.web” />


第四步:最后就是修改web.xml文件了,它在webappWEB-INF下。

web.xml

seckill-dispatcher org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:spring/spring-*.xml seckill-dispatcher /

我们在项目中经常会使用到日志,所以这里还有配置日志xml,在resources文件夹里新建logback.xml文件,所给出的日志输出格式也是最基本的控制台s呼出,大家有兴趣查看logback官方文档

logback.xml

<?xml version="1.0" encoding="UTF-8"?> %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n

到目前为止,我们一共写了7个配置文件,我们一起来看下最终的配置文件结构图

配置文件结构图


SSM框架应用实例(图书管理系统)

一开始想就这样结束教程,但是发现其实很多人都还不会把这个SSM框架用起来,特别是mybatis部分。那我现在就以最常见的“图书管理系统”中【查询图书】和【预约图书】业务来做一个demo吧!

首先新建数据库名为ssm,再创建两张表:图书表book和预约图书表appointment,并且为book表初始化一些数据,sql如下。

schema.sql

– 创建图书表
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=1000 DEFAULT CHARSET=utf8 COMMENT=‘图书表’

– 初始化图书数据
INSERT INTO book (book_id, name, number)
VALUES
(1000, ‘Java程序设计’, 10),
(1001, ‘数据结构’, 10),
(1002, ‘设计模式’, 10),
(1003, ‘编译原理’, 10)

– 创建预约图书表
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),
INDEX idx_appoint_time (appoint_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT=‘预约图书表’


entity包中添加两个对应的实体,图书实体Book.java和预约图书实体Appointment.java

Book.java

package com.soecode.lyf.entity;

public class Book {

private long bookId;// 图书ID

private String name;// 图书名称

private int number;// 馆藏数量

// 省略构造方法,getter和setter方法,toString方法

}

Appointment.java

package com.soecode.lyf.entity;

import java.util.Date;

/**
* 预约图书实体
*/
public class Appointment {

private long bookId;// 图书ID

private long studentId;// 学号

private Date appointTime;// 预约时间

// 多对一的复合属性
private Book book;// 图书实体

// 省略构造方法,getter和setter方法,toString方法

}


dao包新建接口BookDao.javaAppointment.java

BookDao.java

package com.soecode.lyf.dao;

import java.util.List;

import com.soecode.lyf.entity.Book;

public interface BookDao {

/**
* 通过ID查询单本图书
*
* @param id
* @return
*/
Book queryById(long id);

/**
* 查询所有图书
*
* @param offset 查询起始位置
* @param limit 查询条数
* @return
*/
List queryAll(@Param(“offset”) int offset, @Param(“limit”) int limit);

/**
* 减少馆藏数量
*
* @param bookId
* @return 如果影响行数等于>1,表示更新的记录行数
*/
int reduceNumber(long bookId);
}

AppointmentDao.java

package com.soecode.lyf.dao;

import org.apache.ibatis.annotations.Param;

import com.soecode.lyf.entity.Appointment;

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接口不用编写daoImpl, mybatis会给我们动态实现,但是我们需要编写相应的mapper。
mapper目录里新建两个文件BookDao.xmlAppointmentDao.xml,分别对应上面两个dao接口,代码如下。

BookDao.xml

<?xml version="1.0" encoding="UTF-8"?> SELECT book_id, name, number FROM book WHERE book_id = #{bookId} SELECT book_id, name, number FROM book ORDER BY book_id LIMIT #{offset}, #{limit} UPDATE book SET number = number - 1 WHERE book_id = #{bookId} AND number > 0

AppointmentDao.xml

<?xml version="1.0" encoding="UTF-8"?> INSERT ignore INTO appointment (book_id, student_id) VALUES (#{bookId}, #{studentId}) 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}

mapper总结namespace是该xml对应的接口全名,selectupdate中的id对应方法名,resultType是返回值类型,parameterType是参数类型(这个其实可选),最后#{...}中填写的是方法的参数,看懂了是不是很简单!!我也这么觉得~ 还有一个小技巧要交给大家,就是在返回Appointment对象包含了一个属性名为book的Book对象,那么可以使用"book.属性名"的方式来取值,看上面queryByKeyWithBook方法的sql。


dao层写完了,接下来test对应的package写我们测试方法吧。
因为我们之后会写很多测试方法,在测试前需要让程序读入spring-dao和mybatis等配置文件,所以我这里就抽离出来一个BaseTest类,只要是测试方法就继承它,这样那些繁琐的重复的代码就不用写那么多了~

BaseTest.java

package com.soecode.lyf;

import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
* 配置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 {

}

因为spring-serviceservice层的测试中会时候到,这里也一起引入算了!

新建BookDaoTest.javaAppointmentDaoTest.java两个dao测试文件。

BookDaoTest.java

package com.soecode.lyf.dao;

import java.util.List;

import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;

import com.soecode.lyf.BaseTest;
import com.soecode.lyf.entity.Book;

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 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
testQueryById

testQueryAll
testQueryAll

testReduceNumber
testReduceNumber

AppointmentDaoTest.java

package com.soecode.lyf.dao;

import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;

import com.soecode.lyf.BaseTest;
import com.soecode.lyf.entity.Appointment;

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
testInsertAppointment

testQueryByKeyWithBook
testQueryByKeyWithBook


嗯,到这里一切到很顺利那么我们继续service层的编码吧可能下面开始信息里比较大,大家要做好心理准备~

首先,在写我们的控制器之前,我们先定义几个预约图书操作返回码的数据字典,也就是我们要返回给客户端的信息。我们这类使用枚举类,没听过的小伙伴要好好恶补一下了(我也是最近才学到的= =)

预约业务操作返回码说明

返回码说明
1预约成功
0库存不足
-1重复预约
-2系统异常

新建一个包叫enums,在里面新建一个枚举类AppointStateEnum.java,用来定义预约业务的数据字典,没听懂没关系,我们直接看代码吧~是不是感觉有模有样了!

AppointStateEnum.java

package com.soecode.lyf.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;
}

}

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
Enum.java`,用来定义预约业务的数据字典,没听懂没关系,我们直接看代码吧~是不是感觉有模有样了!

AppointStateEnum.java

package com.soecode.lyf.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;
}

}

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
[外链图片转存中…(img-g3xommSZ-1713553112881)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 28
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值