SSM框架快速入门javascript
关于SSM框架环境搭建,请点击这里前往个人博客:SSM框架整合之环境搭建 因为本项目采用了maven,关于IDEA搭建maven项目过程请点击这里前往个人博客:maven起步前端
项目源码请 点击进入 个人GitHub。java
<!--more-->mysql
项目框架:后端:spring+mybatis+springmvc; 前端:bootstrap+Font Awesome图标集 测试环境:IDEA + tomcat8 + mysql5.7 + jdk8 + maven 数据库名称:ssm
1. 实现用户登陆功能 2. 实现客户信息的增删改查功能 3. 实现分页查询功能
备注 如上是一个标准的maven项目,咱们来解释一下各个目录:jquery
img: 放了一些README.md文档所须要得图片,没有实际意义。git
controller: web层,存放springmvc的相关控制器类。github
mapper: 存放接口和映射文件。由于本项目采用了mybatis的接口开发,因此须要将接口和映射文件放在同一目录下,而且名称相同。web
pojo: 存放Java的实体类。ajax
service: 业务层,用于存放一些业务层代码。算法
不要奇怪为何没有出现Dao层,由于本项目相对简单,并无多复杂的逻辑,因此也就必要再写一个Dao层进行扩展。
resources: 是maven项目存放配置文件的根目录,在本例中包含两个子文件夹:
resource
、spring
。前者是存放一些如logback.properties
的配置文件;后者是存放spring的配置文件(spring和springmvc)。my.sql: 存放了关于项目数据库建立和表建立的SQL语句。
fonts: 存放一些字体的配置文件。为了页面的美感,咱们采用了Awesome图标集。
lib: 包含了项目中用到的一些前端类库。
page: 包含全部前端页面。
继上一篇博文:Spring MVC起步其实咱们已经了解了如何整合Spring和Spring MVC框架。那么,接下来咱们就须要了解如何在此基础上整合Mybatis框架。 首先须知Mybatis框架是一个持久层框架,而Spring MVC是WEB层框架,Spring框架则充当着业务层的角色。那么将三者联系起来正好组成了web--service--dao
的三层架构体系。那么整合思路就以下所示了:
SQL语句请查看GitHub中resources目录下的.sql文件 除了建立表,咱们一样要建立pojo
对象,并提供属性setter和getter方法。(注意尽可能保持pojo
属性参数和表字段名称对应相等)
@RequestMapping(value = "/login") public String login(@RequestParam String username,@RequestParam String password, Model model) { User user = userService.login(username); if (user != null) { if (user.getPassword().equals(password)) { //登陆成功 return "page/page"; } else { model.addAttribute("message", "登陆失败"); return "page/loginInfo"; } } else { model.addAttribute("message", "你输入的用户名或密码有误"); return "page/loginInfo"; } }
@RequestMapping
标注login()
方法有两个做用(前提是必须在XML中开启注解扫描<context:component-scan/>
):1.表示该方法是请求处理方法;2.指明了该方法的请求路径。@RequestMapping
能够标记类或方法,分别表示了不一样层级的请求路径。例如当前的login()
方法的请求路径应为:localhost:8080/xxx/login.do
对于请求体中包含多个参数的状况,咱们尽可能用@RequestParam
标记参数,以避免出现未知错误(但这不是必须的)。 用户登陆,咱们首先获取到用户登陆的用户名username
和密码password
,而后根据用户名查询并返回,根据此用户名查询到的密码与登陆的密码进行equals
,若是相等就登陆成功。(固然咱们要判断根据username
查询后的返回值是否为null,不作判断会产生空指针问题,若是一个空值和另外一个值相比显然会报错)。 若是登陆成功,将返回到page/page.jsp
页面(这是根据咱们在springmvc.xml
下配置的视图解析器InternalResourceViewResolver
决定的);若是登陆失败将返回到page/loginInfo.jsp
页面。
<select id="login" parameterType="String" resultType="User"> select * from user where username = #{username} </select>
咱们使用了Mybatis的接口代理开发模式(保证接口和配置文件在同一目录下且名称相同),直接在Mapper.xml
中编写原生sql语句,便可进行与数据库间的交互。 id
指明是哪一个方法调用这个sql;parameterType
指定了接口传递的参数类型(咱们根据用户名查询因此是String类型);resultType
指定该查询语句返回值的数据类型(由于咱们已经在配置文件启用了别名配置typeAliases
,因此这里直接指定pojo对象类名便可;若没有启动别名配置,就必须写类的全限定名)。使用#{}
会将传递的参数值会自动添加""
;注意#{}
和${}
区分,后者则是直接拼接传递进来的字符串,而不会添加任何符号,且前者能避免sql注入。
所谓添加客户信息,就是将JSP中提交的表单数据持久化到数据库中。
建表SQL请看github项目中的resources目录下的.sql文件 一样咱们还要建立对应的pojo
,并提供getter和setter方法。(尽可能保持pojo
中的元素属性名称和表中字段名称相同)。
@RequestMapping(value = "/save") public String save(Customer customer, Model model) { customerService.save(customer); model.addAttribute("message", "保存客户信息系成功"); return "page/info"; }
当点击了提交按钮,表单中的全部数据都应该被持久化到数据库中,而要知道表单中的参数有不少,咱们直接在请求映射方法的参数列表中写参数显然是不可取的,那么咱们若是写一个pojo对象,Spring就会根据这个pojo对象中的属性和JSP表单中的参数进行对应,若是彻底对应那么请求方法会正常执行,一但有某个参数不对应,那么就会报错。(注意咱们表单中并不须要指定id
主键值,由于设计表时已经指定了该id
主键为自增加,即便不指定值,id
依然会自增,你指定了却可能会产生错误,由于到保证每次的id
值都是递增的)。当数据持久化成功,就使用Spring的Model
对象在域对象中设置一个名为message
的值。最后再返回到视图层。
<insert id="save" parameterType="Customer"> insert into customer( c_name, c_telephone, c_address, c_remark) values( #{c_name}, #{c_telephone}, #{c_address}, #{c_remark} ); </insert>
如上这仍然是普通的SQL语句,注意parameterType
如上咱们设置为Customer
其实表明的是cn.tycoding.pojo.Customer
这个对象,由于咱们已经在beans.xml
中启用了mybatis的别名配置。SQL插入语句中不须要指定id
这个字段,缘由是咱们已经配置了id
为自增主键
@RequestMapping(value="/delete") public String delete(@RequestParam int c_id,Model model){ if(customerService.delete(c_id) > 0){ model.addAttribute("message","删除客户信息成功"); return "page/info"; }else{ model.addAttribute("message","删除客户信息失败"); return "page/info"; } }
删除功能只须要根据点击删除按钮时获取到的id
值,在SQL的delete
语句中where
这个id值,便可以实现根据id删除客户信息。
<delete id="delete" parameterType="int"> delete from customer where c_id = #{c_id} </delete>
如此,仍是一个再普通不过的SQL语句,既能够实现根据id删除的功能。
更新客户信息须要咱们实现两个功能:1.再点击编辑按钮时(咱们在按钮设置了onclick="return edit(${xx.id};"
),如此咱们用js监听这个事件,点击了按钮,js获取到id,请求后台根据这个id值查询数据库中的数据。那么咱们先看一下js部分吧:
function edit(id){ $.ajax({ url: 'xxx/findById.do', type: 'POST', dataType: 'json', contentType: 'application/json;charset=UTF-8', data: JSON.stringify({id: id}), success: function(result){ $("#username").val(result.username); $("#password").val(result.password); //继续讲查询到的字段依次进行赋值... }:: }); }
以上实际是一个ajax请求json格式数据:1.type
指定请求类型;2.dataType
指定了服务器返回数据格式类型;3.contentType
指定发送给服务器的数据格式,默认是application/x-www-form-urlencoded
会使此时的data
参数为JSON对象,而设置为application/json
后此时的data
参数就是json字符串了,一样使用stringify()
也是将data
参数转换成json字符串。
@ResponseBody @RequestMapping(value="/findById") public Customer findById(@RequestBody Customer customer){ Customer customer_info = customerService.findById(customer.getC_id()); if(customer_info != null){ return customer_info; }else{ return null; } }
@RequestBody
读取http请求内容,并将数据绑定在映射方法的参数上;@ResponseBody
将映射须要返回的参数转换成指定的数据格式,而因为咱们在ajax请求中指定dataType
为json
,那么@ReqponseBody
将会返回json格式数据。 当ajax请求了这个映射方法,Controller获取到指定的id去调用Service层根据这个id查询数据库select * from customer where id = #{id}
。而后将数据拼装成JSON格式,并返回给页面。最后ajax会走success方法,咱们从返回的数据success:function(result)
中经过result.xx
的方式取出来并经过jquery的val()
方式赋值到指定的位置,那么就实现了数据回显。 实现修改功能,首先要知道本来的数据(数据回显),而后将修改后的数据在此存入到数据库中(update customer set xx=#{xx} where id = #{id}
。那么咱们看一下,更新数据库的Controller:
@RequestMapping(value="/update") public String update(Customer customer,Model model){ int rows = customerService.update(customer); if(rows > 0){ model.addAttribute("message","更新客户信息成功"); return "page/info"; }else{ model.addAttribute("message","更新客户信息失败"); return "page/info"; } }
由于更新数据其实就是简单的提交表单,表单提交后访问这个映射方法,而后查询数据库,若是正常更新了数据,sql影响行数应该大于0(rows>0),那么就更新成功,经过SpringMVC的Model方法向request域对象中存入成功信息,在返回的页面中,经过${message}
EL表达式的方式取出提示信息。 最后咱们看一下更新的SQL如何写:
<!-- 更新客户信息的方法 --> <update id="update" parameterType="Customer"> update customer set c_id = #{c_id}, c_name = #{c_name}, c_telephone = #{c_telephone}, c_address = #{c_address}, c_remark = #{c_remark} where c_id = #{c_id} </update>
解释以前咱们先看一下分页查询的页面:
public class PageBean<T> implements Serializable { //当前页 private int pageCode; //总页数=总记录数/每页显示的记录数 private int totalPage; //总记录数 private int totalCount; //每页显示的记录数 private int pageSize; //每页显示的数据 private List<T> beanList; }
由于咱们要实现分页查询,因此没法避免一些参数,这里直接将其封装为一个JavaBean就是为了方便调用,而配置自定义泛型<T>
就是为了供多个对象的调用,若是你在对Customer类进行分页查询,那么在调用时只须要new pageBean<Customer>()
便可将查询的数据绑定为Customer
类的数据;对其余类进行分页亦是这样。
pageCode: 表示当前(你点击)的是第几页。
totalPage: 表示总页数(总页数=总记录数/每页显示的记录数)。经过
select count(*) from 表
查询到总记录数,每页显示的记录是用户指定的;那么总记录数/每页显示几条记录就获得了一共有几页(前端页面展现)。totalCount: 表示总记录数,由SQL:
select count(*) from 表
查询到该表咋数据库中一共多少条记录数。pageSize: 表示每页显示的记录数,这个是用户决定的,好比咱们想让每页显示5条数据,那么这个
pageSize
就应该是5,即每页会显示5条记录。beanList: 表示当前显示的数据。经上面的一系列查询和封装,咱们最终须要将数据返回给页面,而这些须要返回给页面的数据最终会被封装到这个
beanList
中,在jsp中使用<forEach>
标签遍历beanList
获得封装的数据并显示到页面上。
/** * 客户信息列表(分页查询功能) */ @RequestMapping(value="/findByPage") public String findByPage(@RequestParam(value="pageCode",defaultValue = "1",required = false)int pageCode, @RequestParam(value="pageSize",defaultValue = "2",required = false)int pageSize, HttpServletRequest request, Model model){ // 封装分页数据 String c_name = request.getParameter("c_name"); String c_telephone = request.getParameter("c_telephone"); Map<String,Object> conMap = new HashMap<String,Object>(); conMap.put("c_name",c_name); conMap.put("c_telephone",c_telephone); // 回显数据 model.addAttribute("page",customerService.findByPage(pageCode,pageSize,conMap)); return "page/list"; }
对比上面两张图,发现,用户能够指定的就是pageCode
当前页和pageSize
每页显示的记录数。因此在点击按钮(好比点击页码3
),就会提交表单并访问Controller的findByPage()
方法。 那么Controller就须要接受这两个参数:pageCode
andpageSize
,而且咱们设置:defaultValue
默认值;required
是否必须指定(若是没有写false,在每次请求这个方法时就必须指定这个参数的具体值,否则就会报错)。 方法体中咱们还经过request
域获取c_name
和c_telephone
(由于要实现条件查询:输入信息,查询数据)。 最后咱们将这些查询条件封装到Map集合中,而后调用Service层,将pageCode
和pageSize
以及封装的查询条件信息conMap
一同传入Service层。
首先咱们看一下由Controller调用的Service层接口: 因为咱们在Controller调用Service的
findByPage()
方法时并无给定返回值什么类型,因此这里咱们要手动将其修改成PageBean<Customer>
。下面看一下实现类如何编写:
public PageBean<Customer> findByPage(int pageCode, int pageSize, Map<String, Object> conMap) { HashMap<String,Object> map = new HashMap<String,Object>(); PageBean<Customer> pageBean = new PageBean<Customer>(); //封装当前页 pageBean.setPageCode(pageCode); pageBean.setPageSize(pageSize); // 封装总记录数(从数据库中查询) int totalCount = customerMapper.selectCount(); System.out.println("查询到的总记录数:"+totalCount); pageBean.setTotalCount(totalCount); //封装总页数 double tc = totalCount; Double num = Math.ceil(tc / pageSize); pageBean.setTotalPage(num.intValue()); // 设置limit分页查询的起始位置和终止位置 map.put("start",(pageCode - 1) * pageSize); map.put("size",pageBean.getPageSize()); //封装每页显示的数据 List<Customer> list = customerMapper.findByPage(map); pageBean.setBeanList(list); // 分页查询功能也要封装显示起始页和终止页 conMap.put("start",(pageCode - 1) * pageSize); conMap.put("size",pageBean.getPageSize()); // 封装 List<Customer> listCondition = customerMapper.findCondition(conMap); pageBean.setBeanList(listCondition); return pageBean; }
做为业务层,固然负责梳理业务信息,首先咱们须要将Controller传入进来的pageCode
和pageSize
封装进PageBean
的相关属性中。而后查询总记录数(经过select count(*) from 表
查询获得),根据总记录数pageCount
和前台传入的pageSize
每页显示的记录数计算获得总页数,一样封装到PageBean
中,最后咱们要设置分页的起始位置start
和数量size
,调用Mapper查询数据库中的数据,将数据封装到beanList
中便可。可是要注意咱们其实写了两个分页查询的方法:findByPage()
和findCondition()
由于二者都须要分页展现到页面上。最后解释两点:
start
为*(当前页-1)此时每页显示的记录数*。 设置size
为咱们在pageBean中封装的每页显示几条记录数。 例如:咱们目前页面每页显示2条数据,点击下一页,则显示的数据就是第3 - 5
条。<!-- 查询总的记录数 --> <select id="selectCount" resultType="int"> select count(*) from customer; </select> <!-- 分页查询 --> <select id="findByPage" parameterType="Map" resultMap="BaseResultMap"> select * from customer <if test="start != null and size != null"> limit #{start},#{size} </if> </select> <!-- 多条件查询 --> <select id="findCondition" parameterType="Map" resultMap="BaseResultMap"> <!-- where 1=1 能够保证where后的语句永远是正确的 由于在where后的动态SQL可能会执行也可能不会不会执行,若是没有执行,那么where后将跟一个空值,那么显然这样是会报错的 --> select * from customer where 1 = 1 <if test="c_name != null and c_name != ''"> and c_name like concat('%', #{c_name}, '%') </if> <if test="c_telephone != null and c_telephone != ''"> and c_telephone like concat('%', #{c_telephone}, '%') </if> <!-- 咱们经过在Service中的计算决定了咱们每次请求的数据应该从那一页开始,那一页结束 --> <if test="start != null and size != null"> limit #{start},#{size} </if> </select>
注意几点:
首先咱们看一下页码是如何展现出来的:
百度分页算法(每页显示10个页码): 当点击页码7以后的页码,最前端的页码依次减小 [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] 点击[7] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] 算法: 若 总页数 <= 10 则begin=1 end=总页数 若 总页数 > 10 则begin=当前页-5 end=当前页+4 头溢出: 若begin < 1 则begin=1 end=10 尾溢出: 若end > 总记录数 则brgin=end-9 end=总页数 此项目设置每页显示5个页码: 若 总页数 <= 5 则begin=1 end=总页数 若 总页数 > 5 则begin=当前页-1 end=当前页+3 头溢出: 若begin < 1 则begin=1 end=5 尾溢出: 若end > 总记录数 则brgin=end-4 end=总页数 * (end表示页面的最后一个页码,begin表示页面的第一个页码)
以前有人会问道这个头溢出和尾溢出是什么意思?其实仔细看看,若是咱们安装上面设计的算法逻辑:头溢出就是指当页数不少一直点击上一页,为避免出现第0页而设置的;那么尾溢出就以下图所示状况了:
到此为止,咱们基本讲完了SSM框整合的过程,你是否看明白了呢?其实整合SSM框架并不难,按照这个思路,咱们学习完SSM框架整合,就能够着手练习一些小项目了。详细过程,你们能够从个人项目源码中分析。
<br/>
若是你们有兴趣,欢迎你们加入个人Java交流群:671017003 ,一块儿交流学习Java技术。博主目前一直在自学JAVA中,技术有限,若是能够,会尽力给你们提供一些帮助,或是一些学习方法,固然群里的大佬都会积极给新手答疑的。因此,别犹豫,快来加入咱们吧!
<br/>
If you have some questions after you see this article, you can contact me or you can find some info by clicking these links.