吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 7334|回复: 21
收起左侧

[Java 转载] 一个案例带你快速入门SSM框架开发

  [复制链接]
Ty_tutu 发表于 2018-6-9 19:57
本帖最后由 Ty_tutu 于 2018-6-9 20:12 编辑

手把手教你入门SSM框架开发

关于SSM框架环境搭建,请点击这里前往我的博客:SSM框架整合之环境搭建

由于本项目采用了maven,关于IDEA搭建maven项目过程请点击这里前往我的博客:maven起步

项目源码请 点击进入 我的GitHub。

关于项目

项目环境

项目框架:后端:spring+mybatis+springmvc; 前端:bootstrap+Font Awesome图标集
测试环境:IDEA + tomcat8 + mysql5.7 + jdk8 + maven
数据库名称:ssm

项目功能

1. 实现用户登录功能
2. 实现客户信息的增删改查功能
3. 实现分页查询功能

项目结构

备注

如上是一个标准的maven项目,我们来解释一下各个目录:

img: 放了一些README.md文档所需要得图片,没有实际意义。

controller: web层,存放springmvc的相关控制器类。

mapper: 存放接口和映射文件。因为本项目采用了mybatis的接口开发,所以需要将接口和映射文件放在同一目录下,并且名称相同。

pojo: 存放Java的实体类

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的三层架构体系。那么整合思路就如下所示了:

1. 整合dao(即mapper),实现Mybatis和Spring的整合

2. 整合service,由Spring管理service接口,在service中可以调用dao(mapper)

3. 整合web(controller),实现Spring MVC和Spring的整合,在controller中调用service

需求实现

  1. 实现用户登录功能

1.1 创建表结构

SQL语句请查看GitHub中resources目录下的.sql文件

除了创建表,我们同样要创建pojo对象,并提供属性setter和getter方法。(注意尽量保持pojo属性参数和表字段名称对应相等)

1.2 编写Controller层

@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页面。

1.3 编写Mapper.xml

<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注入。

  1. 实现客户信息的添加

所谓添加客户信息,就是将JSP中提交的表单数据持久化到数据库中。

2.1 创建表结构

建表SQL请看github项目中的resources目录下的.sql文件

同样我们还要创建对应的pojo,并提供getter和setter方法。(尽量保持pojo中的元素属性名称和表中字段名称相同)。

2.2 编写Controller层

@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的值。最后再返回到视图层。

2.3 编写Mapper.xml

<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为自增主键

  1. 实现客户信息的删除功能

3.1 编写Controller层

@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删除客户信息。

3.2 编写Mapper.xml

<delete id="delete" parameterType="int">
    delete from customer where c_id = #{c_id}
</delete>

如此,还是一个再普通不过的SQL语句,既可以实现根据id删除的功能。

  1. 更新客户信息

更新客户信息需要我们实现两个功能: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字符串。

4.1 编写Controller层

@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如何写:

4.2 编写Mapper.xml

<!-- 更新客户信息的方法 -->
<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>
  1. 分页查询

解释之前我们先看一下分页查询的页面:

5.1 封装PageBean

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得到封装的数据并显示到页面上。

5.2 jsp页面

5.3 编写Controller层

/**
 * 客户信息列表(分页查询功能)
 */
@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就需要接受这两个参数:pageCodeandpageSize,并且我们设置:defaultValue默认值;required是否必须指定(如果没有写false,在每次请求这个方法时就必须指定这个参数的具体值,不然就会报错)。

方法体中我们还通过request域获取c_name和c_telephone(因为要实现条件查询:输入信息,查询数据)。

最后我们将这些查询条件封装到Map集合中,然后调用Service层,将pageCode和pageSize以及封装的查询条件信息conMap一同传入Service层。

5.4 编写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()因为两者都需要分页展示到页面上。最后解释两点:

  1. 计算总页数:总页数 = 总记录数 / 每页显示的记录条数。这里用到的ceil()方法:返回大于或登录参数的最小double值,并等于数学整数。如double a = 5;double b = 3;ceil(a/b) = 2.0。最后用Double类的intValue()方法返回此Double值作为int类型的值。
  2. mysql为分页查询提供了limit方法,limit a,b就是读取第a条到第b条的所有记录。
    设置start为(当前页-1)此时每页显示的记录数。
    设置size为我们在pageBean中封装的每页显示几条记录数。
    例如:我们目前页面每页显示2条数据,点击下一页,则显示的数据就是第3 - 5条。   

5.5 编写Mapper.xml

  • 注意:这里使用了mybatis的mapper接口实现方式,再强调几个注意事项:

    1. mapper.xml文件名称必须和接口名称相同
    2. Mapper 接口方法名和 UserMapper.xml 中定义的每个 sql 的 id 同名。
    3. Mapper 接口方法的输入参数类型和 UserMapper.xml 中定义的 sql 的parameterType 类型相同。
    4. Mapper 接口的返回类型和 UserMapper.xml 中定义的 sql 的 resultType 类型相同

    <!-- 查询总的记录数 -->
    <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>

注意几点:

  1. findByPage方法是用来分页显示数据的,我们传进来的数据是Map集合,定义了parameType="Map";resultMap实现了将查询到的复杂的数据映射到一个结果集中
  2. findCondition方法是用来分页显示条件查询到的数据的,注意where 1 = 1主要是用来避免以下动态sql中的条件都不满足的情况时,where后就没数据了,这样显然报错,加上1=1就避免了这种情况

5.6 分页逻辑

首先我们看一下页码是如何展示出来的:

百度分页算法(每页显示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框架整合,就可以着手练习一些小项目了。详细过程,大家可以从我的项目源码中分析。

项目运行截图

img-1.png
img-2.png
img-3.png
0.png

免费评分

参与人数 6吾爱币 +9 热心值 +6 收起 理由
暴躁的鹅卵石 + 1 + 1 用心讨论,共获提升!
一块大番薯 + 1 + 1 用心讨论,共获提升!
烟火清凉 + 1 + 1 谢谢@Thanks!
wushaominkk + 3 + 1 鼓励转贴优秀软件安全工具和文档!
超人就是我1 + 2 + 1 我很赞同!
烟花易冷xx + 1 + 1 热心回复!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| Ty_tutu 发表于 2018-6-9 19:59
本帖最后由 Ty_tutu 于 2018-6-9 20:05 编辑

大家还是去我的博客:www.tycoding.cn 看看吧,吾爱上对Markdown的支持不好
或则直接去我的GitHub上看README文档,自己花了好长时间写的,如果对大家有帮助,也希望大家能给star一下呀,小弟在此谢过各位大佬了。
(注:本人是大一在校生,目前一直在自学,技术有限,如果项目文档解释中有不对的地方,欢迎联系告知我)
 楼主| Ty_tutu 发表于 2018-6-9 21:47
Setsuro 发表于 2018-6-9 21:39
太厉害了 辛苦喽

辛苦就谈不上了,只是为了记录些笔记,加深印象

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
Setsuro + 1 + 1 我很赞同!

查看全部评分

Eternity-Myth 发表于 2018-6-9 21:15
最近我也在开发一个SSM架构的管理系统项目,楼主写得很好,很详细,非常棒
提点建议或者说改进吧,使用mybatis-generator可以非常快捷地逆向生成所需的pojo(或者叫entity)实体类,dao和mapper,可以直接用了,自己定义功能也可以在这些文件改写,这样构建项目会快速一些。还有,分页可以用pagehelper,非常方便地构建分页
LaoJII 发表于 2018-6-9 20:29
你也太恐怖了吧。。大一就这么强
超人就是我1 发表于 2018-6-9 20:38
兄得  这波操作可以
 楼主| Ty_tutu 发表于 2018-6-9 20:44
硬日菊花憋样红 发表于 2018-6-9 20:29
你也太恐怖了吧。。大一就这么强

哈哈,因为对Java比较感兴趣呐
 楼主| Ty_tutu 发表于 2018-6-9 20:44
超人就是我1 发表于 2018-6-9 20:38
兄得  这波操作可以

蟹蟹蟹蟹,一同进步
中华田园史努比 发表于 2018-6-9 21:25
大一就这么厉害。。。
yd131421 发表于 2018-6-9 21:37
看不懂,过来学习了
Setsuro 发表于 2018-6-9 21:39
太厉害了 辛苦喽
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-5-15 11:08

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表