SSM之旅

经常在百度 google 做伸手党,知乎 github stackoverflow瞎逛,在很多大神的博客中学了很多,也在知乎上面看到,一个有好习惯的程序员是应该在工作一年开始写博客的,虽然不知道该写些什么和怎么写,但是我觉得慢慢开始养成这个习惯也是好的。
在公司用了快2年的EJB和structs2了,大半个月前开始入手ssm,mybatis入门还算简单,经过一周的空闲时间学习,就能写mapper 增删改查,也知道能够用逆向工程MBG根据table构建po 和mapper,:
MBG构建的po与mapper
整合到spring让spring代理mybatis也ok:
测试Mapper

applicationContext.xml

SqlMapConfig.xml

那么现在当然是要整合一下SSM,做一个基于BS SSM的增删改查系統。
公司用的工具是netbeans,所以就用netbeans创建java web工程,刚创建的javaweb项目是可以run的,但是applicationContext.xml 和dispatcher-servlet.xml我不想放在web的文件夹下了,想放到classpath下并进行分类整理/config/spring /config/mybatis ,放了applicationContext.xml之后,在web.xml 修改如下

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>

嗯~可以run

把dispatcher-servlet.xml放到/config/spring 下,修改配置如下:
web.xml


run欢迎页index.jsp没问题
但是发送请求的时候,tomcat就报这个错:
No mapping found for HTTP request with URI [/SpringMybatisDemo/test.do] in DispatcherServlet with name 'dispatcher'
根据字面意思,request 在dispatcher这个servlet找不到mapping
心里问:“搞什么,我不是在web.xml配置了么?”
然后答:“呵呵,你配置了controller了么?”

在dispatcher-servlet.xml增加一个bean(controller)

<!--controller-->
 <bean name="/test" class="demo.controller.TestController"></bean>

然后写一个controller

public class TestController extends AbstractController {
    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ModelAndView modelAndView = new ModelAndView("test");
        modelAndView.addObject("message", "hello world");
        return modelAndView;
    }
}

test.jsp是这样的:

<body>
    <h1>TEST OK!</h1>
    <h1>${message}</h1>
</body>

通过这个链接:http://localhost:8090/SpringMybatisDemo/test.do


就能看到这个信息了:

TEST OK!

hello world

吼,已经可以正确收发请求了,那么现在开始搞SSM。


先搞搞student的信息吧,写一个student的controller,继承AbstractController
现在这个StudentMapper已经由spring代理了,但是我还是要获得spring的context才能获得StudentMapper的对象,于是写了个StudentService专门为StudentController服务:
StudentService.java

StudentService的上层是DefaultService:
DefaultService.java
但是这样我的Studentcontroller就要new 一个StudentService的实例了,增加了耦合度,有没什么办法让我把StudentService注入到StudentController里面呢,于是就想到了把service注入到controller里。
既然想到就开始干吧,于是被一堆错误卡了很久......
后来重新换了个tomcat
在网上查了一下,很多推荐用SpringContextUtil implements ApplicationContextAware,这里我也用这种方法吧:
SpringContextUtil.java
在applicationContext.xml中再加上这个bean:
                
<bean id="springContextUtil" class="demo.anderson.util.SpringContextUtil" />
                
            
加上service:
                 
<bean id="studentService" class="demo.anderson.service.StudentService"> 
                
            
在StudentController中也要注入studentService:
                 
<bean name="/student" class="demo.controller.StudentController">
       <property name="studentService">
         <ref bean="studentService"/>        
       </property>
     </bean>
      
                    

在StudentController.java中加入StudentService注入口:
StudentService studentService;
                 
public void setStudentService(StudentService studentService) {
  this.studentService = studentService;
}
                     
                

这样访问student.do的时候,就会跳到StudentController中,然后调用studentService的方法,从数据库中找到Student信息,返回到页面上:
student.jsp:

                 
 &ltbody> 
   <title>Student</title> 
     <h1>Student</h1> 
     <h1>${student.sno}||${student.sname}${student.sage}</h1> 
   </body>
                        
                

Student

s001||张三||23

CRUD,我们继续来进行其他的操作,要进行操作,就得有个操作界面吧。

嗯,虽然是简陋了一点,但是至少是可以操作的了:
student.jsp

为了有更好的用户体验,那编辑的动作我们就不跳转吧,我让编辑这个操作映射回StudentController里的不同方法,用@RequestMapping是可以做到,但是用xml怎么配置呢?上网找找看。
。。。。。。

找是找到了,旧版本的spring是哦那个MultiActionController 实现多动作controller的,有两种方法,一种是controller 继承MultiActionController ,另一种是定义代理对象,使用delegate参数指定controller。 现在感觉是挺麻烦的,所以这里我们还是哦那个@RequestMapping的方法吧

既然要用注解,那就得告诉spring我现在不单单在xml里面配置了吧,还得找一下注解的配置
开启注解:
<mvc:annotation-driven/> //如果不开启注解驱动,注解就无效了
那么既然用了@RequestMapping注解了,我们的Controller也用注解的方式吧,不然同一个bean同时存在注解与xml配置看起来不爽维护起来还怪。
用注解方式把一个controller加入bean的方式是在该controller类的上方写上@Controller
                         
import org.springframework.stereotype.Controller;
@Controller
public class StudentController {
...
}
                        
                    
要想这个controller bean起作用,还要告诉spring我有这个bean:
                         
<context:component-scan base-package="demo.anderson.controller" />

                        
                    
那么这个时候,@Controller的注解是不是就相当于替代掉下面这一段xml配置了呢?
                         
 <!--controller-->

<!--  <bean class="demo.controller.StudentController" >

    <property name="studentService">

      <ref bean="studentService"/>        

    </property>

  </bean>-->

                        
                    
还差一个property,现在配置的这个StudentController没有注入StudentService,这个时候需要用到注解@Autowired
@Autowired
StudentService studentService;


StudentController.java

通过@Controller 和@Autowired 就摆脱了在xml配置<bean>和<property>的繁琐步骤了,其实在现在已经是spring4.3的版本了,完全可以用全注解配置取代xml配置,但是还是一步步来吧。


我们来run一下:
student.jsp

ok,现在可以继续做“编辑”操作了,用同一个controller。
不跳转,自然想到用ajax,这里我们没有用表单,所以就用超链接的方式,以GET的方法把参数放在 url里。
                        
                            $.ajax({
                    url: "./findStudentBySno.do?sno=" + sno,
                    success: function (data, textStatus, jqXHR) {
                      alert(data);
                    }
                  });
                        
                


后台用@RequestParam获取 前端通过请求url 传过来的参数,并包装成JSONObject 回写到页面中:
                         
@RequestMapping("/findStudentBySno")
public void findStudentBySno(
HttpServletRequest request,
HttpServletResponse response,
@RequestParam("sno") String sno) throws IOException {
Student student = studentService.findStudentBySno(sno);
JSONObject jSONObject = new JSONObject(student);
response.getWriter().write(jSONObject.toString());
}
                        
                    

前端获取到json数据并对json数据进行解析:

student.jsp

student.jsp

现在是可以达到大概的效果了,可是到前端的中文都变成了问号“?”,这应该是编码不一致导致的,后台调一下编码试试。
response.setCharacterEncoding("UTF-8");
在StudentController中加上这段代码,前端的中文就可以正常显示了。
但如果每个controller都要加,代码就重复了,能不能在更外层的地方,只设置一次,之后就不用再设置了呢?

spring这里也为开发者提供了方法:
                         
<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>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
                        
                    
在web.xml中加入以上配置,就能让程式默认呢使用utf-8编码。以上配置大部分都比较好理解,有点不好理解的地方是forceEncoding,
                         
                forceEncoding == false:
                request.setCharacterEncoding("UTF-8");
                
                forceEncoding == true:
                request.setCharacterEncoding("UTF-8");
                response.setCharacterEncoding("UTF-8");
                
                        
                    
所以这里我们需要设置为true。

后台传到前台的数据是json格式,那前台传到后台的格式也用json好了:
                         
function doSubmit() {
$.ajax({
url: "./updateStudentBySno.do?sno=" + sno,
type: 'POST',
data: {
sname: $("#sname").val(),
ssex: $("#ssex").val(),
sage: $("#sage").val(),
sno: $("#sno").val()
},
dataType: 'json',
success: function (data, textStatus, jqXHR) {
alert(data);
}
});
}
                        
                    
service要增加一个方法了,这里发现service中的mapper每次都要初始化,同样造成了代码重用,所以这里也在web.xml中配置一下mapper的bean,然后把mapper注入到service里:
                         
<bean id="studentService" class="demo.anderson.service.StudentService">
<property name="studentMapper" ref="studentMapper"/>
</bean>
<bean id="studentMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="demo.anderson.mapper.StudentMapper"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>                        
                    
Studentservice的studentMapper属性也增加一个setter方法:
                         
                public void setStudentMapper(StudentMapper studentMapper) {
                this.studentMapper = studentMapper;
                }
                        
                    
service和controller都加上update方法:
                         
public int updateStudentBySno(Student student) {
return studentMapper.updateByPrimaryKey(student);
}
@RequestMapping("/updateStudentBySno")
public void updateStudentBySno(HttpServletRequest request,
HttpServletResponse response,
@RequestParam("sno") String sno) throws IOException {
HashMap<String, String> hm = new HashMap();
HashMap<String, String[]> hm2 = (HashMap) request.getParameterMap();
for (String key : hm2.keySet()) {
hm.put(key, hm2.get(key)[0]);
}
Student student = new Student();
student.setSage(Short.parseShort(hm.get("sage")));
student.setSname(hm.get("sname"));
student.setSno(hm.get("sno"));
student.setSsex(hm.get("ssex"));
int result = studentService.updateStudentBySno(student);
response.getWriter().write(result);
}                        
                    
这个时候发现,update之后,事务并没有自动提交,现在暂时不想配置事务管理,所以把datasource的配置换成一个有自动commit的bean
                         
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.url}"
p:username="${jdbc.username}"
p:password="${jdbc.password}"
p:defaultAutoCommit="true"  />                        
                    
加上defaultAutoCommit,嗯 大功告成:


CRUD,还差增删,那我们就搞一搞增加一个学生:
新增学生和更新学生的操作是一样的,用的是同一个form。
删除一个学生:
                         
function deleteStudentBySno(sno) {
var is_delete = confirm("是否要删除该学生? " + sno);
if (is_delete == true) {
$.ajax({
url: "./deleteStudentBySno.do?sno=" + sno,
success: function (data, textStatus, jqXHR) {
location.reload();
alert(data);
},
error: function (jqXHR, textStatus, errorThrown) {
alert("error:" + jqXHR + textStatus + errorThrown);
}
});
}
}                        
                    
增加&删除学生:

到这里,单表的增删改查就完成了,接下来要进行两张表的操作。

这里被MBG 生成的一个Key类搞了一段时间,我发现其中一个关联table的po类并没有key类,后来又去生成一次,仔细观察MBG日志才发现,在table里面的不同用户下还有一个同名的table,原来如此,drop掉其他用户的table,继续。

发现界面有点不好看,加了些bootstrap的样式:


先把Score的增删改查做一下:
由于Score是load出来的,为了增加用户体验,在编辑完成之后希望直接看到更改:


success: function (data, textStatus, jqXHR) {
 window.opener.$("#load_div").load("../sc/findScoreBySno.do?sno=" + $("#sno").val() + "#sc_table", function () {
 alert(data);
 window.close();
 });                        
                    
增加体验,又调了一些前端的东东,到这里 Student 和Score两个表的增删改查都完成了,但是现在没有涉及到多表,这里能看到Score的课程名还是自己的,现在我们要抓取Course中的cname,

查了一下,MBG生成的mapper 和po 并美欧生成与多表查询有关的东西,所以这里我们需要修改一下mapper 和po才能达到多表查询的需求:
在ScMapper.xml中加入这一段:
                         
<resultMap id="ScAndCourseMap" type="demo.anderson.po.Sc">
<id column="SNO" jdbcType="VARCHAR" property="sno" />
<id column="CNO" jdbcType="VARCHAR" property="cno" />
<result column="SCORE" jdbcType="DECIMAL" property="score" />
<collection property="course" ofType="demo.anderson.po.Course">
<id column="TNO" jdbcType="VARCHAR" property="tno" />
<id column="COURSE_CNO" jdbcType="VARCHAR" property="cno" />
<result column="CNAME" jdbcType="VARCHAR" property="cname"></result>
</collection>
</resultMap>
<select id="selectScAndCourse" parameterType="demo.anderson.po.ScKey" resultMap="ScAndCourseMap">
select a.sno, a.cno, a.score, b.cno course_no, b.cname, b.tno
from sc a, course b
<where>
a.cno = b.cno
<if test="sno != null">
and a.sno = #{sno}
</if>
</where>
</select>                        
                    
然后在Sc的po中加上private Course course 以及getset方法。

单元测试一下,通过,然后写到程式里:


多表的查询完成了。

AOP:
现在该搞搞AOP了,在这里业务上就假设insert update 和delete的操作要写个log吧。
这里先搭一下AOP:
java.lang.NoClassDefFoundError: org/aopalliance/aop/Advice
deploy的时候报出了这个错误,百度了一下,发现少了个必不可少的包:

Alliance.jar

为什么必不可少spring不整合到自己的jar包里面啊?
一直遇到缺少包的问题,都一一补上,这里也补一下AOP的配置吧:
                         
 <bean id="logHandler" class="demo.anderson.aop.LogHandler"></bean>
 <aop:config>
 <aop:aspect id="log" ref="logHandler">
 <aop:pointcut id="addLog" expression="execution( * demo.anderson.controller.StudentController.*(..))" />
 <aop:before method="beforeLog" pointcut-ref="addLog" />
 <aop:after method="afterLog" pointcut-ref="addLog" />
 </aop:aspect>
 </aop:config>
 ------------------------------------------------------------------------------------------------------
 package demo.anderson.aop;
 /**
 * @author anderson
 */
 public class LogHandler {
 public void beforeLog() {
 System.out.println("-----------------------------------beforeLog()-----------------------------------");
 }
 public void afterLog() { 
 System.out.println("-----------------------------------afterLog()-----------------------------------");
 }
 }                        
                    
配置到这里,就能实现StudentController的所有操作,都会输出:

但是如果我想配置整个Controller包下的所有方法呢?
                         
<aop:pointcut id="addLog" expression="execution(* demo.anderson.controller.*.*(..))" />
按照正常逻辑当然是这么配置,但实际上,这样配置就报错了,具体原因不明,已经在论坛上提问了,但是这里这么配置是没问题的:  
                    
<aop:pointcut id="addLog" expression="execution(* demo.anderson.controller.StudentController.*(..)) || execution(* demo.anderson.controller.ScoreController.*(..))" />          
如果想获得切点方法的参数,在xml配置可以这么加:
<aop:pointcut id="addLog" expression="execution(* demo.anderson.controller.StudentController.*(..)) || execution(* demo.anderson.controller.ScoreController.*(..)) and args(..)" />
用JoinPoint jp来获取参数:
public void beforeLog(JoinPoint jp) {
        Object[] args = jp.getArgs();
        for (Object obj : args) {
            if (obj instanceof String) {
                System.out.println("obj:" + obj);
            }
        }
        System.out.println("-----------------------------------beforeLog()-----------------------------------");
    }

obj:s001
-----------------------------------beforeLog()-----------------------------------
DEBUG [http-nio-8090-exec-115] - Creating a new SqlSession
DEBUG [http-nio-8090-exec-115] - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@49771d96] was not registered for synchronization because synchronization is not active
DEBUG [http-nio-8090-exec-115] - Fetching JDBC Connection from DataSource
DEBUG [http-nio-8090-exec-115] - JDBC Connection [jdbc:oracle:thin:@10.5.132.150:1521:whi2, UserName=WHSD, Oracle JDBC driver] will not be managed by Spring
DEBUG [http-nio-8090-exec-115] - ==>  Preparing: select SNO, SNAME, SAGE, SSEX from STUDENT where SNO = ? 
DEBUG [http-nio-8090-exec-115] - ==> Parameters: s001(String)
DEBUG [http-nio-8090-exec-115] - <==      Total: 1
DEBUG [http-nio-8090-exec-115] - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@49771d96]
DEBUG [http-nio-8090-exec-115] - Returning JDBC Connection to DataSource
DEBUG [http-nio-8090-exec-115] - Returning cached instance of singleton bean 'logHandler'
-----------------------------------afterLog()-----------------------------------

评论

热门博文