学习了一段时间spring,springMVC和Mybatis,从开始学到现在熬了好多夜晚,好几个深夜和bug作战,真是难受。
打算写系列学习笔记,第一篇从一个小的成果说起吧,刚刚学的看这篇可能有点吃力,有个大概把握或者跳过之后再看都可以。项目在IEDA中完成,之前我写java都用 eclipse ,最开始也不想转到 IDEA, 现在觉得:真香。
1 资源配置
1.1资源依赖
这个是本次Maven项目中的版本信息,具体如下,下面大概介绍一下。
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
<spring.version>5.2.12.RELEASE</spring.version>
<javax.servlet.version>4.0.1</javax.servlet.version>
<mysql.connector.java.version>8.0.21</mysql.connector.java.version>
<jstl.version>1.2</jstl.version>
<druid.version>1.2.3</druid.version>
<mybatis.version>3.5.3</mybatis.version>
<mybatis.spring.version>2.0.3</mybatis.spring.version>
<slf4j.version>1.7.25</slf4j.version>
<log4j.version>1.2.17</log4j.version>
</properties>
spring, springMVC 和 Mybatis 的关系大概是这样的,通俗来说,springMVC是负责和网页打交道的模块,直接负责与网页的信息交互。mabatis是专门和数据库打交道的模块。中间的逻辑处理可以放让spring来做或者面向对象编程也是可以用过来的。
说到面向对象编程(OOB),那就要大概说一下spring中利用的面向切面编程(AOP)啦,其实面向切面编程主要的目的就是将功能分离,例如你想在程序中加入写入日志的代码,加入函数很多,直接和程序写在一起修改起来就很费劲了,如果可以将二者分离维护起来就很容易。大概思想是这样,具体这篇就不说啦。
1.1.1 spring依赖
这个pom文件中是spring的相关依赖
<!-- spring start-->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<!-- <version>5.2.9.RELEASE</version>-->
<version>${spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context-support -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-expression -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-web -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- spring end-->
1.1.2 springMVC依赖
springMVC的相关依赖
<!-- springMVC start-->
<!-- https://mvnrepository.com/artifact/jstl/jstl -->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>${jstl.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${javax.servlet.version}</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- springMVC end-->
1.1.3 myBatis依赖
myBatis和连接mySql数据库的相关依赖
<!-- mybatis start-->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.connector.java.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis.spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- mybatis end-->
1.1.4 日志文件和测试依赖
日志的相关依赖
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
<!-- <scope>test</scope>-->
</dependency>
1.2配置文件
1.2.1 web.XML配置
一般来说,这个需要配置两个过滤器:
1 POST请求从网页端返回到服务端的时候,可能会发生汉字乱码的, 所以需要配置 CharacterEncodingFilter,放在所有filter的前面会好点。 filter-mapping中pattern设置为 /* 可以理解为多所有网页生效
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2 一般可以把GET请求做为请求数据(数据库中select)来用,POST用作更新(update),PUT用做插入数据(insert), DELETE用作删除(delete)。将POST 请求映射为PUT或者DELETE请求需要配置 HiddenHttpMethodFilter
<filter>
<filter-name>hiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
然后需要在文件中引用spring的配置文件(applicationContext.xml)的位置,其中,classpath:一般指的是创建了项目后的resources文件夹。再配置前端控制器,就是已经封装好的和网页打交道的一个模块。一般这样配置就可以。web》xml配置完成
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
1.2.2 spring配置文件(applicationContext.xml)的配置。
spring一大功能就是不用在new一个对象,而是由注解或者xml配置创建按对象,在spring的配置文件中可以选择你想要让哪些包中有标注的类成为对象,context:component-scan base-package="包名"就是做这个的。
另外整合在一起的spring需要配置数据库,开启数据库的事务注解,然后让spring自动创建用到的bean对象。当然,spring会给你实现很多功能的同时,你还是需要提供一个接口来调用的,其中调用数据库操作中数据库语言写在xml中和程序语言分离,怎么去对应每一对函数和数据库查询语言呢?当然就需要配置一个类似map的东西啦。不要有疑惑,其实mabadis有自己的配置文件,可以写数据库相关信息,但是整合后的直接写在spring配置文件中,会省事很多。
其中,涉及到jdbc.properties,存放连接数据库的信息,让程序知道你的要用哪个表,用户名密码什么的一般是这样:注意和配置源中的键值对应上。
driver=com.mysql.cj.jdbc.Driver
jdbcUrl=jdbc:mysql://localhost:3306/[表的名字]?useSSL=false&serverTimezone=UTC
user=root
password=123
applicationContext.xml 配置是这样
<context:component-scan base-package="com.yzp"/>
<!-- 配置数据库相关参数-->
<context:property-placeholder location="classpath:jdbc.properties" ignore-unresolvable="true"/>
<!-- 数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="${driver}"/>
<property name="url" value="${jdbcUrl}"/>
<property name="username" value="${user}"/>
<property name="password" value="${password}"/>
</bean>
<!-- 基于注解的事务管理-->
<bean id="transactionMannager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionMannager"/>
<!-- 配置SqlsessionFactory对象-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
<bean id="selSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="com.yzp.dao"/>
</bean>
</beans>
1.2.3 数据库配置文件(mybatis-config.xml)
数据库配置文件本来可配置七八项,但是有很大一部分在spring配置文件中配置了,所以数据库一般不用配置,最多是这两个,开启懒加载机制这部分也许用到。这个项目没有用到,可加可不加(懒加载机制就是多次查询的时候,如果后面的查询数据没有用到,就不查询了,大概这样)
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="true"/>
</settings>
1.2.4 springMVC配置文件(dispatcher-servlet.xml)
最后一个重量级配置文件,一般需要配置的有扫描包,大概是说让那些包下的类成为和网页打交道的类,当然需要注解才行。然后配置一下你的文件的前缀后缀
<context:component-scan base-package="com.yzp.controller"/>
<mvc:annotation-driven></mvc:annotation-driven>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
当然,一般网页是能直接访问WEB-INF下的文件的,但是每次都通过控制层进行网页定向是没有必要的,没有数据交互的时候明明可以直接访问网页,于是你需要告诉代码当你访问哪个网页后缀的时候定向到哪个文件上,同时还需要让这种直接定向不影响你本来的后端前端交互操作( mvc:annotation-driven/)。于是这两个配置来啦。放问 /add的网页会定向到update.jsp这个文件中。mvc:default-servlet-handler/这个指的是开启静态资源的访问。
<mvc:view-controller path="add" view-name="update"/>
<mvc:default-servlet-handler/>
<mvc:annotation-driven/>
1.2.5日志文件配置(log4j.properties)
###set log levels
log4j.rootLogger = DEBUG,Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.Target=System.out
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.ConversionPattern= %d{ABSOLUTE} %5p %c{1}:%L - %m%n
到此,一个完整的SSM框架的配置完成,其实可以当作模板来用,必要是做一些小的修改。
2 图书表增删改查
2.1 demn说明
其实表很简单,只要issn,书名name和价钱price三项,要完成的就是在网页端,利用ssm框架实现数据库的查询显示,修改数据,删除数据,和添加数据。大概如下。issn我设置的是自增主键,其实这个无所谓,我们的重点不是数据库,数据库的操作自行百度,建立(issn,name,pricce)这样的表很容易的操作。
2.2 项目结构
项目结构是这样,model放的是书的类,dao包中放的是和数据库交互的接口,service放的的逻辑代码,是spring层,在这个项目其实这一层没有用,就是规范一下,正常这里应该会有很多后端的业务逻辑处理代码,我们这里就不搞了。让后service就是和网页交互的springMVC的代码啦。
2.2.1 Book类
很简单的,和数据库数据一样,名称一样会省事很多,都是issn name price,然后有toString方法以及get和set方法,类中无需注解。
public class Book {
private Integer issn;
private String name;
private Integer price;
@Override
public String toString() {
return "Book{" +
"issn=" + issn +
", name='" + name + '\'' +
", price=" + price +
'}';
}
public Integer getIssn() {
return issn;
}
public void setIssn(Integer issn) {
this.issn = issn;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
}
2.2.2 Dao层
由于Mabatis的存在,Dao层接口的实现会由Mabatis实现,所以你需要的只是设计你需要的方法,通过加注解的方式告诉系统这些要和数据库交互,同时给每个方法配置上SQL语句。BookDao接口如下
@Repository
public interface BookDao {
List<Book> findAll();//查询所有书
Book queryByIssn(Integer issn);//根据issn查询
void deleteBook(int issn);//根据issn删除
void addBook(Book book);//添加书籍
void updateBook(Book book);//更新信息
}
相关的sql语句的配置文件,这里需要注意,一般Dao层的接口类名字和XML文件sql语句配置名字要相同,要不然会报没有绑定的错误。BookDao.xml 文件放在resources文件夹下的mapper下,这是我们在spring的配置文件(applicationContext.xml)中指定的。
BookDao中放着sql语句,需要指出的有两点,一个是namespace,这个指的是你的BookDao类的路径,而每一个查询语句的id都要和你BookDao接口中对应的名字相同。这两个哪个不对的话都会报没有绑定的错误。
<mapper namespace="com.yzp.dao.BookDao">
<sql id="bookField">
b.issn as "issn",
b.name as "name",
b.price as "price"
</sql>
<select id="findAll" resultType="com.yzp.model.Book">
select
<include refid="bookField"/>
from book as b
</select>
<select id="queryByIssn" resultType="com.yzp.model.Book">
select issn, name, price from book where issn = #{issn}
</select>
<insert id="addBook">
insert into book(name, price) values (#{name}, #{price})
</insert>
<delete id="deleteBook">
delete from book where issn = #{issn}
</delete>
<update id="updateBook">
update book set name=#{name}, price=#{price} where issn=#{issn}
</update>
</mapper>
2.2.3 service层 @Service
这个写的规范一点,定义了我们函数的一个接口,然后提供了它的实现类,这个demn中是没啥用其实。
接口除了没有注解和BookDao是一样的,其实是想说Dao层提供接口主要是匹配SQL语句,操作数据库的。而这里的接口是程序逻辑,是你的程序业务应该具有的功能。
BookService接口是这样:
public interface BookService {
List<Book> fingAll();
Book queryByIssn(Integer issn);
void updateBook(Book book);
void deleteBook(int issn);
void addBook(Book book);
}
以及它的实现类,其实实现类就是调用Dao层的函数,我们没有加入其它的业务逻辑代码,比较简单。需要注意两点,@Service注解表示这是Service层的类,@Resource这样的注解就是Sping的强大之一的地方,它会自动去找程序中相关的类,然后自动给你赋值。一般spring 创建的bean对象,也就是类,名字是类名首字母小写。如果遇到比如BookDao。当然,如果程序中只有这一个类对象的话名字就不重要啦。
@Service
public class BookServiceImp implements BookService {
@Resource
private BookDao bookDao;
@Override
public List<Book> fingAll() {
return bookDao.findAll();
}
@Override
public Book queryByIssn(Integer issn) {
return bookDao.queryByIssn(issn);
}
public void updateBook(Book book){
bookDao.updateBook(book);
}
@Override
public void deleteBook(int issn) {
bookDao.deleteBook(issn);
}
@Override
public void addBook(Book book) {
bookDao.addBook(book);
}
}
2.2.4 控制层 @Controller
最后是控制层啦,和网页打交道吗,肯定要给出网址吧,所以你可以在类外加@RequestMapping(value = “/book”)这样的注解,表示最开始的路径名加上/book,用于区分不同的类,然后类中函数继续加,继续区分函数。
既然要操作数据库,那么就通过和她之间联系的Service层操作,于是自动赋值注解【@Resource private BookService bookService;】这个来啦。刚刚BookService中已经实现了数据库的增删改查,哪个Controller层就是要调用这个函数修改后端数据,同时修改前端界面。所以下面的函数基本都是一行操作数据库,然后重定向页面这样。
其中queryByIssn函数是为更新函数服务的,更新我们写的是用户拿到用户要更新的书籍issn到数据库查,然后将数据信息展示到网页让用户修改。
return后这个字符串"redirect:/book/findAll",中,redirect指的是要重定向网页,转到/book/findAll这个网页下。ModelAndView就是个可以携带数据到网页的网页,哈哈哈
@Controller
@RequestMapping(value = "/book")
public class BookController {
@Resource
private BookService bookService;
@RequestMapping(value = "/updateBook", method = RequestMethod.PUT)
public String updateBook(Book book){
bookService.updateBook(book);
return "redirect:findAll";
}
@RequestMapping(value = "/query/{issn}")
public String queryByIssn(@PathVariable("issn") Integer issn, Map<String, Book> map){
Book book = bookService.queryByIssn(issn);
map.put("book", book);
return "update";
}
@RequestMapping(value = "/del/{id}", method = RequestMethod.DELETE)
public String deleteBook(@PathVariable("id") Integer issn){
bookService.deleteBook(issn);
System.out.println(issn);
return "redirect:/book/findAll";
}
@RequestMapping(value = "/updateBook", method = RequestMethod.POST)
public String addBook(Book book){
bookService.addBook(book);
System.out.println(book);
return "redirect:findAll";
}
@RequestMapping(value = "/findAll", method = RequestMethod.GET)
public ModelAndView findAll(Model model){
ModelAndView mv = new ModelAndView("bookList");
List<Book> bookList = bookService.fingAll();
mv.addObject("Booklist", bookList);
for(Book book: bookList){
System.out.println(book);
}
return mv;
}
}
index.jsp文件
第一个程序,Hello world 走一波,然后导向到图书的列表中,就是bookList.jsp文件
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@page isELIgnored="false" %>
<html>
<body>
<h2>Hello World!</h2>
<%--<a href="${pageContext.request.contextPath}/test/hello">图书列表</a>--%>
<a href="${pageContext.request.contextPath}/book/findAll">图书列表</a>
</body>
</html>
bookList.jsp 就是这一小节开头的图中的样子,就是用表来显示书籍信息,然后定义了一个函数为删除数据信息服务,更新和插入数据信息都是导到update网页。都是前端的代码,不解释啦
<script type="text/javascript" src="${pageContext.request.contextPath}/static/js/jquery-1.7.2.js"></script>
<head>
<title>图书列表</title>
<script type="text/javascript">
$(function(){
$("a[id^=delete]").click(function (){
var href = $(this).attr("href");
console.log("ok")
$("#deleteForm").attr("action", href);
$("#deleteForm").submit();
return false;
});
});
</script>
</head>
<body>
<form id="deleteForm" method="post">
<input type="hidden" name="_method" value="DELETE">
</form>
<table id="bookList" border="1px" align="center" width="70%" cellpadding="10px" cellspacing="0px">
<tr align="center">
<th>issn</th>
<th>name</th>
<th>price</th>
<tr>
</tr>
<c:forEach items="${Booklist}" var="book">
<tr align="center">
<td>${book.issn}</td>
<td>${book.name}</td>
<td>${book.price}</td>
<td>
<a href="${pageContext.request.contextPath}/book/query/${book.issn}">编辑</a>
<a id="delete-${book.issn}" href="${pageContext.request.contextPath}/book/del/${book.issn}">删除</a>
</td>
</tr>
</c:forEach>
<td align="center" colspan="4"> <a href="${pageContext.request.contextPath}/add">添加数据</a> </td>
</table>
</body>
</html>
update中是这样。修改书籍信息会从数据库查到一本书过来,所以book不空,走PUT请求,而插入是没有的,是空的,走post请求,issn数据库自增,不需要输入。
<head>
<title>新增||修改数据</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/book/updateBook" method="post">
<c:if test="${!empty book}">
<input type="hidden" name="_method", value="PUT">
<input type="hidden" name="issn" value="${book.issn}">
</c:if>
书名:<input type="text" name="name" value="${book.name}"></br>
价格:<input type="text" name="price" value="${book.price}">
<input type="submit" value="提交">
</form>
3 结尾
学起来还是很费解的,各种bug调到你没脾气,不过学会一点感觉还是挺不错的,框架吗,但从最终的使用来看,真的很简便。代码没有注释,修改后传到github,近期如有需要或者交流,可以到发邮件1175686648@qq.com找我。