SSM框架整合
这里说的SSM整合,主要说的是Spring和mybatis之间的整合。因为spring和springMVC都是spring生态系统中的框架,所以spring和springMVC之间的整合是无缝的整合,即,我们在不知不觉中,其实spring和springMVC已经整合好了,不用你做什么额外的事情了。但是mybatis是第三方的框架,所以我们要手动把spring和mybatis进行整合。
原始整合,即,我们每一层之前怎么学的(mybatis管理dao层,springMVC管理view层),这里就怎么直接用。但是原始整合,你会发现这种整合中,spring和mybatis的整合度不是很高。所以我们之后会解决这些问题,这些问题解决了,SSM框架的整合就已经OK了。这里,我们先学原始的整合方式,然后我们解决原始整合方式存在的问题,解决完了,我们本节“SSM框架整合”的学习就结束了。
原始整合方式
原始整合方式,就是我们直接用SSM框架来做web项目,所以整合的步骤:无。即,你想怎么写这个项目,就怎么写,该用框架的时候就直接用框架来写代码就行了。
没有整合的步骤,那我们来看看我们平常写项目的步骤就行了,平常写项目的步骤做完了,原始整合就结束了。
下面用一个例子来做演示:
步骤:
-
编写数据库和表
建库如下:
建表如下:
-
创建maven项目,并且创建maven工程可能需要的包。反正就是把项目的结构搭好。
-
导入项目可能需要用到的架包
<?xml version="1.0" encoding="UTF-8"?> <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/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.itcast</groupId> <artifactId>ssm</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.5.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.7</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.0.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.0.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.0.5.RELEASE</version> </dependency> <!--servlet和jsp--> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.0</version> </dependency> <!--mybatis相关的架包--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.5</version> </dependency> <!--这个mybatis-spring是我们之后优化整合的时候会用,原始整合不用。--> <!-- <dependency>--> <!-- <groupId>org.mybatis</groupId>--> <!-- <artifactId>mybatis-spring</artifactId>--> <!-- <version>1.3.1</version>--> <!-- </dependency>--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <!--source 和 target根据个人需要自己修改--> <source>11</source> <target>11</target> </configuration> </plugin> </plugins> </build> </project>
-
编写实体类
实体类如下:
package com.itcast.domain; public class Account { //建议写Integer,而不是写int private Integer id; private String name; private Double money; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Double getMoney() { return money; } public void setMoney(Double money) { this.money = money; } }
注意点:
实体类用定义属性的时候,建议包装类型来定义。理由如下:
-
编写Mapper接口和可能会用到的方法
package com.itcast.mapper; import com.itcast.domain.Account; import java.util.List; public interface AccountMapper { public void save(Account account); public List<Account> findAll(); }
-
编写Service层的接口和可能会用到的方法
package com.itcast.service; import com.itcast.domain.Account; import java.util.List; public interface AccountService { public void save(Account account); public List<Account> findAll(); }
-
编写Service层的接口实现类和实现方法
package com.itcast.service.impl; import com.itcast.domain.Account; import com.itcast.service.AccountService; import java.util.List; public class AccountServiceImpl implements AccountService { @Override public void save(Account account) { } @Override public List<Account> findAll() { return null; } }
service实现类的具体实现,我们先空着,之后写。
-
编写Controller
这里我们具体实现也先空着。先把框架搭好。
-
编写前端的页面
注意:
因为WEB-INF下的资源是受保护的,所以我们不能直接访问到。我们放在WEB-INF文件夹外的资源,我们可以浏览器输入地址直接访问。但是在WEB-INF文件夹内的资源,我们只能通过controller的转发机制访问。
-
编写配置文件(这些配置文件的结构先搭好,里面具体写什么得用得时候才知道,所以我们先空着)
-
补齐配置文件中的配置:
AccountMapper.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.itcast.mapper.AccountMapper"> <insert id="save" parameterType="account"> insert into account value(#{id},#{name},#{money}) </insert> <select id="findAll" resultType="Account"> select * from account </select> </mapper>
applicationContext.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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 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 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd "> <!--组件扫描 --> <context:component-scan base-package="com.itcast"> <!--我们扫这个com.itcast包,那么com.itcast包下的@Controller注解也会被扫描到,但是这个@Controller注解的类一般是表示层的类,所以我们到时候应该让他被springmvc来管理的,所以我们这里spring就不去控制它了,我们就需要这个@Controller注解给排除掉去,到时候让springmvc的配置文件来扫描--> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> </beans>
jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/ssm jdbc.username=root jdbc.password=815924
log4j.properties
### direct log messages to stdout ### log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target=System.out log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n ### direct messages to file mylog.log ### #log4j.appender.file=org.apache.log4j.FileAppender #log4j.appender.file.File=hibernate.log #log4j.appender.file.layout=org.apache.log4j.PatternLayout #log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n ### set log levels - for more verbose logging change 'info' to 'debug' ### log4j.rootLogger=all, stdout
spring-mvc.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.xsd"> <!--组件扫描,主要扫描@Controller注解--> <context:component-scan base-package="com.itcast.controller" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!--配置mvc注解驱动--> <mvc:annotation-driven/> <!--配置内部资源视图解析器--> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/pages/"></property> <property name="suffix" value=".jsp"></property> </bean> <!--开放静态资源访问--> <mvc:default-servlet-handler/> </beans>
sqlMapConfig.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> <!--加载properties文件--> <properties resource="jdbc.properties"></properties> <!--定义别名--> <typeAliases> <!--<typeAlias type="com.itcast.domain.Account" alias="account"></typeAlias>--> <!--上面这种定义别名的方式是,把这个com.itcast.domain.Account定义一个别名,别名叫account--> <!--下面我们介绍另一种定义别名的方式,扫描包的方式。比如下面这样写的话,我们这个com.itcast.domain包下所有的类都将会被设置别名。设置什么别名,看例子:比如,我们这个com.itcast.domain包下有一个Account类和User类,那么,你用了下面这个扫描包的这个方式,就相当于给这个com.itcast.domain包下的Account类设置了account和Account两个别名。给com.itcast.domain包下的User类设置了User和user两个别名--> <package name="com.itcast.domain"></package> </typeAliases> <!--数据源环境--> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"></transactionManager> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <!--加载映射文件--> <mappers> <!--<mapper resource="com\itcast\mapper\AccountMapper.xml"/>--> <!--上面这个加载映射文件的方式是一个个加载的,所以,要想加载多个映射文件,你得写多个mapper标签。下面,我们来介绍另一种写法,包引入方式,我们在“mybatis-day1.md”中的mappers标签里面讲过,但是使用这种方式有一些注意点,你看之前“mybatis-day1.md”中的mappers标签的笔记就行. --> <package name="com.itcast.mapper"/> </mappers> </configuration>
-
完成逻辑代码的编写
package com.itcast.controller; import com.itcast.domain.Account; import com.itcast.service.AccountService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.servlet.ModelAndView; import java.util.List; @Controller @RequestMapping("/account") public class AccountController { @Autowired private AccountService accountService; //保存(实际中,我们保存成功应该会跳转到一个列表页面的,这里我们简单点,就直接在页面打印一个字符串意思一下。) @RequestMapping("/save") @ResponseBody public String save(Account account){ accountService.save(account); return "保存成功!"; } //查询。查询结束我们返回一个数据,并且返回一个视图。 public ModelAndView findAll(){ List<Account> accountList=accountService.findAll(); ModelAndView modelAndView=new ModelAndView(); modelAndView.addObject("accountList",accountList); modelAndView.setViewName("accountList"); return modelAndView; } }
package com.itcast.service.impl; import com.itcast.domain.Account; import com.itcast.mapper.AccountMapper; import com.itcast.service.AccountService; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.springframework.stereotype.Service; import java.io.IOException; import java.io.InputStream; import java.util.List; @Service("accountService") public class AccountServiceImpl implements AccountService { @Override public void save(Account account) { try { InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); AccountMapper mapper = sqlSession.getMapper(AccountMapper.class); mapper.save(account); sqlSession.commit(); sqlSession.close(); } catch (IOException e) { e.printStackTrace(); } } @Override public List<Account> findAll() { try { InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); AccountMapper mapper = sqlSession.getMapper(AccountMapper.class); List<Account> accountList = mapper.findAll(); sqlSession.close(); return accountList; } catch (IOException e) { e.printStackTrace(); } return null; } }
-
测试
结果:
数据库:
发现,页面显示的时候出现了乱码,但是数据库里面存数据的时候是正确的。
分析:
数据库能正确存数据的原因是:我们在web.xml中设置了乱码过滤器,所以服务器获取前端代码的数据的乱码问题解决了,即,我们前端输入了中文的数据,后端正确获取到了中文数据,没有出现乱码,然后存到数据库里面,所以数据库里面存的是正确的。
那么前端出现乱码的原因是什么呢?你看我们之前没用ssm的时候,我们都直接在servlet的代码块的后半段设置response.setContentType(“text/html;charset=utf-8”)。这样前端页面就不会出现乱码了,这里我们ssm要达到这个效果,我们也得设置一下响应的编码。
解决方法如下:
测试:
点击"保存"后:
数据库数据如下:
-
下面我们来完成保存的方法。之前1~13步中,没有把save()方法和他跳转的页面完成,下面我们来把他们完成。
-
测试:
原始整合方式的弊端
弊端:
你看上面这里service层中,每次执行某个方法的时候都会执行下面这些语句,即,都会加载配置文件、创建工厂对象、创建Session对象、事务提交、关闭sql会话等。
解决:
把创建Session工厂的工作、创建AccountMapper对象的工作都交给spring容器来做。并且把事务控制也交给spring来做。
即,我们要做到让spring创建这个AccountMapper,并且注入到这个AccountServiceImpl的某个AccountMapper类型的成员变量里面,然后这个AccountServiceImpl类里面的所有方法都可以用那个成员变量了,这个类里面也不用创建对象,因为创建对象和绑定到AccountServiceImpl的成员变量的工作都交给spring容器了。
然后我们在spring核心配置文件里面给这个AccountServiceImpl类里面的方法配置上事务控制,这样,那些被设置了事务控制的方法会在执行成功的时候自动提交,执行失败的时候自动回滚了,就取代了我们上面的sqlSessioni.commit()和sqlSessioni.close()了。其实上面这个例子里面,出现异常回滚的代码都没有写,我们通过spring核心配置文件给AccountServiceImpl类里面的方法配置上事务控制,那些方法执行出现异常,就会自动回滚了,也相当于补齐了上面例子中没有写的回滚的功能。
具体怎么整合看下面。
优化后的整合
我们先在spring核心配置文件中加一些配置做到让spring来创建工厂和AccountMapper对象。
注意:上面这个工厂类是mybatis-spring包下的,所以我们要把之前注释掉的mybatis和spring整合需要用到的架包给解开。
然后我们就可以把mybatis核心配置文件中那些被移到spring核心配置文件中来配置的东西给删了
删除前:
删除后:
修改AccountServiceImpl。
上面是完成了让spring帮我们创建AccountMapper对象。下面我们来把声明式事务控制做了,这样优化后的SSM整合就ok了。
添加事务控制如下:
测试:
数据库之前数据:
输入:
点击保存后:
数据库数据:
输入http://localhost:8080/ssm_war_exploded/account/findAll,显示的结果如下
OK,整合完成,并且测试通过。