给Java开发人员的Play Framework(2.4)介绍 Part2:使用Play,Spring,JPA进行开发

1. 介绍

这篇文章会使用Play。Spring,JPA(hibernate)开发一个简单的CRUD功能,主要是为了介绍怎样使用Play进行开发。

2. 界面截图

非常easy的新增和查询功能。我们来看看代码怎样实现。

3. 代码实现

1. Model

代码架构使用典型的MVC,分层为Controller-Service-Dao-Model。

首先来看Model。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
//省略了部分字段
@Table(name = "test_object")
@Entity
public class TestObject implements EntityClass<Integer>, OperableData {

    private Integer id;

    private String orderNo;

    /**
     * 状态
     */
    private TestObjectStatus status;

    //...
    /**
     * 下单时间
     */
    private DateTime buyTime;
    //...

    private List<TestObjectItem> testObjectItemList = new ArrayList<>(0);


    @OneToMany(fetch = FetchType.LAZY, mappedBy = "testObject")
    public List<TestObjectItem> getTestObjectItemList() {
        return testObjectItemList;
    }

    public void setTestObjectItemList(List<TestObjectItem> testObjectItemList) {
        this.testObjectItemList = testObjectItemList;
    }

    @GeneratedValue(strategy = GenerationType.AUTO)
    @Id
    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }


    @Column(name = "status")
    @Enumerated(EnumType.STRING)
    public TestObjectStatus getStatus() {
        return status;
    }

    public void setStatus(TestObjectStatus status) {
        this.status = status;
    }


    @Column(name = "buy_time")
    @Type(type="org.jadira.usertype.dateandtime.joda.PersistentDateTime")
    public DateTime getBuyTime() {
        return buyTime;
    }

    public void setBuyTime(DateTime buyTime) {
        this.buyTime = buyTime;
    }


}

非常普通的Entity,使用JPA注解。唯一须要注意的是在处理枚举类型和Joda DateTime类型的时候用到了不同的类型注解。

2. Dao
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
//省略了部分方法
@Repository
public class GeneralDao {

    @PersistenceContext
    EntityManager em;

    public GeneralDao(){}

    public GeneralDao(EntityManager em) {this.em = em;}

    public EntityManager getEm() {
        return em;
    }

    /**
     * 使用jpql进行查询
     * @param ql jpql
     * @param page 分页对象,可选
     * @param queryParams 查询參数
     * @param <T>
     * @return
     */
    public <T> List<T> query(String ql, Optional<Page<T>> page, Map<String, Object> queryParams) {
      //...
    }

    /**
     * 使用jpql进行数据更新操作
     * @param ql
     * @param queryParams
     * @return
     */
    public int update(String ql, Map<String, Object> queryParams) {
      //...
    }

    public <T extends EntityClass<Integer>> void persist(T t) {
        setOperableDataIfNecessary(t, t.getId() == null || t.getId() == 0);
        em.persist(t);
    }

    public <T extends EntityClass<Integer>> T merge(T t) {
        setOperableDataIfNecessary(t, t.getId() == null || t.getId() == 0);
        return em.merge(t);
    }

    public <T extends EntityClass<Integer>> boolean remove(T t) {
        if(t != null) {
            em.remove(t);
            return true;
        } else {
            return false;
        }
    }

    public <T extends EntityClass<Integer>> boolean removeById(Class<T> type, Integer id) {
        T t = get(type, id);
        return remove(t);
    }

    public <T extends EntityClass<Integer>> T get(Class<T> type, Integer id) {
        return em.find(type, id);
    }

    public void flush() {
        em.flush();
    }

    public <T extends EntityClass<Integer>> void refresh(T t) {
        em.refresh(t);
    }

    public <T extends EntityClass<Integer>> void detach(T t) {
        em.detach(t);
    }
}

这里使用的是通用Dao。一般的增删改查操作能够直接通过该Dao完毕。能够看出这个Dao仅仅是对JPA的EntityManager一个简单封装。 大部分操作还是委派给EntityManager完毕。

代码中也能够直接取得EntityManager进行操作。

3. Service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
//省略了部分方法
@Service
public class TestObjectService {

    @PersistenceContext
    EntityManager em;

    @Autowired
    GeneralDao generalDao;

    @Transactional(readOnly = true)
    public List<TestObject> findByKey(Optional<Page<TestObject>> page, Optional<String> orderNo, Optional<TestObjectStatus> status,
            Optional<DateTime> createTimeStart, Optional<DateTime> createTimeEnd) {

        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<TestObject> cq = cb.createQuery(TestObject.class);
        Root<TestObject> order = cq.from(TestObject.class);

        List<Predicate> predicateList = new ArrayList<>();
        if(orderNo.isPresent()) {
            predicateList.add(cb.equal(order.get("orderNo"), orderNo.get()));
        }
        if(createTimeStart.isPresent()) {
            predicateList.add(cb.greaterThanOrEqualTo(order.get("createTime"), createTimeStart.get()));
        }
        if(createTimeEnd.isPresent()) {
            predicateList.add(cb.lessThanOrEqualTo(order.get("createTime"), createTimeEnd.get()));
        }
        if(status.isPresent()) {
            predicateList.add(cb.equal(order.get("status"), status.get()));
        }

        cq.select(order).where(predicateList.toArray(new Predicate[predicateList.size()])).orderBy(cb.desc(order.get("updateTime")));

        TypedQuery<TestObject> query = em.createQuery(cq);

        if(page.isPresent()) {
            CriteriaQuery<Long> countCq = cb.createQuery(Long.class);
            countCq.select(cb.count(countCq.from(TestObject.class))).where(predicateList.toArray(new Predicate[predicateList.size()]));
            Long count = em.createQuery(countCq).getSingleResult();
            page.get().setTotalCount(count.intValue());

            query.setFirstResult(page.get().getStart());
            query.setMaxResults(page.get().getLimit());
        }

        List<TestObject> results = query.getResultList();

        if(page.isPresent()) {
            page.get().setResult(results);
        }

        return results;
    }


    @Transactional(readOnly = true)
    public TestObject get(Integer id) {
        return generalDao.get(TestObject.class, id);
    }

}

findByKey方法是一个查询方法,这里使用的是JPA的Criteria查询。Service类没有使用接口,仅仅有实现类。

Service就是一个Spring管理的Bean, 事务边界在Service层。

4. Controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//省略了部分方法
@org.springframework.stereotype.Controller
public class TestObjectController extends Controller {

    @Autowired
    private TestObjectService testObjectService;


    public Result list(String status, String orderNo) {

        List<TestObject> testObjectList = testObjectService.findByKey(of(PageFactory.getPage(request())), ofNullable(orderNo),
                ofNullable(status).map(TestObjectStatus::valueOf), empty(), empty());

        return ok(list.render(testObjectList));
    }

    public Result addPage() {
        return ok(add.render(Form.form(TestObject.class)));
    }

    public Result updatePage(Integer id) {
        return ok(update.render(Form.form(TestObject.class).fill(testObjectService.get(id))));
    }

    //...
}

Controller继承play.mvc.Controller。和SpringMVC一样,在Play中。Controller就是一系列Action的集合。比如我开发用户有关的功能, 那么我就建一个UserController,然后把用户的CRUD方法都放在UserController里。每一个方法都有自己的路由规则。

这里我们先来看list方法:

1
2
3
4
5
6
7
public Result list(String status, String orderNo) {

    List<TestObject> testObjectList = testObjectService.findByKey(of(PageFactory.getPage(request())), ofNullable(orderNo),
            ofNullable(status).map(TestObjectStatus::valueOf), empty(), empty());

    return ok(list.render(testObjectList));
}

这里list的功能是查询出全部满足条件的測试对象(TestObject对象)。首先来看參数,这里声明了两个查询參数status和orderNo。用来匹配Http请求QueryString中的參数, 这和SpringMVC中声明了RequestParam(“status”)的參数相似。注意有一点差别。这里的status和orderNo不能捕捉通过Http Body提交的參数。仅仅能匹配QueryString中的參数。

Controller中调用service方法来完毕查询,然后将结果返回。方法的返回值声明是play.mvc.Result接口。你能够理解Result的实现类仅仅须要包括两个值: ResponseHeader和ResponseBody。对于ResponseHeader的设置,这里调用父类的ok方法设置返回的Http状态码为200。相应的还有created 201, notFound 404等方法。 ok方法參数须要传入的就是ResponseBody,參数类型声明为play.twirl.api.Content特质(Scala中的特质相似于Java的接口)。你基本上永远不须要去手动构造这个特质的实现, 而是使用Play提供的模板。

这里我在views.html.test文件夹下有一个list.scala.html模板。这个模板文件会被IDE自己主动编译成views.html.test.list类,类里面有一个render方法来完毕模板的渲染, render方法返回值就是play.twirl.api.Content的子类。所以我在这里能够直接调用list.render(testObjectList)方法来完毕模板的渲染。

好了,如今来对比SpringMVC,假设是用SpringMVC的话,这种方法应该是这种

1
2
3
4
5
6
7
8
9
10
11
@RequestMapping(value = "/test/objects", method = RequestMethod.GET)
@ResponseStatus(HttpStatus.OK)  //可省略
public String list(String status, String orderNo, Model model) {

    List<TestObject> testObjectList = testObjectService.findByKey(of(PageFactory.getPage(request())), ofNullable(orderNo),
            ofNullable(status).map(TestObjectStatus::valueOf), empty(), empty());

    model.addAttribute("testObjectList", testObjectList);

    return "views/html/test/list";
}

细心的你可能已经发现了。Play的版本号与SpringMVC的对比,少了一个路由的信息。那么在Play中怎么配置路由呢,请看下节

5. routes文件(路由)

在Play中,全部的路由信息都是统一放在一个文件中。即conf/routes文件。

上面的list方法路由在routes中相应例如以下:

1
GET         /test/objects                     @controllers.test.TestObjectController.list(status ?= null, orderNo ?= null)

最左边的GET声明的是Http Method,在Play中每一个路由都要明白写出相应的Http Method。中间是路由的URI,最右边是映射的Controller方法。參数status ?

= null代表參数是可选的, 假设请求參数中没有status则默认值是null。routes文件的一大优点是在写映射Controller方法的时候IDE能帮助自己主动补全,而且编译器在编译的时候也能校验声明的參数个数与类型是否一致, 这能有效的帮助开发人员降低错误。

路由也配好了,剩下的工作就是模板的编写。

6. 模板

list.scala.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
@(testObjects: List[ordercenter.models.TestObject])

@import common.utils.DateUtils._

@main()() {

    <div class="breadcrumbs" id="breadcrumbs">
        <script type="text/javascript">
        try{ace.settings.check('breadcrumbs' , 'fixed')}catch(e){}
        </script>

        <ul class="breadcrumb">
            <li>
                <i class="icon-home home-icon"></i>測试对象管理
            </li>
            <li class="active"><a href="@controllers.test.routes.TestObjectController.list()">測试对象查询</a></li>
        </ul><!-- .breadcrumb -->
    </div>

    <div class="page-content">
        <div class="page-header">
            <h1>
                測试对象管理
                <small>
                    <i class="icon-double-angle-right"></i>測试对象查询
                </small>
            </h1>
        </div>
            <!-- /.page-header -->

        <div class="row">
            <div class="col-xs-12">
                    <!-- PAGE CONTENT BEGINS -->

                <div class="row">
                    <div class="col-xs-12">
                        <div class="table-responsive">
                            <table id="sample-table-1" class="table table-striped table-bordered table-hover">
                                <thead>
                                    <tr>
                                        <th>状态</th>
                                        <th>买家</th>
                                        <th class="hidden-480">金额</th>
                                        <th>下单时间</th>
                                        <th></th>
                                    </tr>
                                </thead>

                                <tbody>
                                @for(testObject <- testObjects) {
                                    <tr>
                                        <td>@testObject.getStatus.value</td>
                                        <td>@testObject.getBuyerId</td>
                                        <td>@testObject.getActualFee</td>
                                        <td>@printDateTime(testObject.getCreateTime) </td>
                                        <td>
                                            <div class="visible-md visible-lg hidden-sm hidden-xs btn-group">
                                                <button class="btn btn-xs btn-info" onclick="location.href='@controllers.test.routes.TestObjectController.updatePage(testObject.getId)'">
                                                    <i class="icon-edit bigger-120"></i>
                                                </button>
                                                <button class="btn btn-xs btn-success" onclick="location.href='@controllers.test.routes.TestObjectController.list()'">
                                                    <i class="icon-info bigger-120"></i>
                                                </button>
                                            </div>
                                        </td>
                                    </tr>
                                }

                                </tbody>
                            </table>
                        </div><!-- /.table-responsive -->
                    </div><!-- /span -->
                </div>
            </div>
        </div>
    </div>
}

Play的模板引擎是Twirl。关于这个引擎的介绍能够參考上一篇文章

@(testObjects: List[ordercenter.models.TestObject])是參数声明,写Play模板的时候,建议參数都通过这种声明的形式传入,而不是使用页面隐藏对象。 由于编译器能够自己主动帮我们校验类型和个数。重构起来也会更方便。

以下一行@import common.utils.DateUtils._引入了定义好的一个工具类, 以下的@printDateTime(testObject.getCreateTime)用来格式化显示时间。

@main()() {...}的形式是调用main模板完毕渲染。main模板前两个參数能够省略,第三个參数须要传入html代码。

在Scala中,方法调用既能够用小括号,比方println("ok"), 也能够用大括号println{"ok"}。而这里第三个參数使用的是大括号。

@for(testObject <- testObjects){}是循环的写法,这里循环testObjects,取出每一条记录用来显示。

onclick="location.href='@controllers.test.routes.TestObjectController.list()'"绑定了onClick事件,用户在点击的时候会跳转到測试对象的编辑页面。 这里没有硬编码uri。而是使用routes反向路由的写法。之所以能这样写,是由于IDE在编译的时候会依据routes文件自己主动生成一个routes对象, 对象里面的方法相应的就是我们配置好的controller方法映射。这里写的@controllers.test.routes.TestObjectController.list()在模板渲染的时候就会被替换成/test/objects这个URI。

4. 总结

这一篇主要介绍了Play在整合Spring和JPA之后是怎样进行开发的。能够看出,开发Play应用与开发SSH应用没有太大差别。仅仅是Controller和模板的写法有所不同, 可是我们能非常快享受到Play的便利:简单易用的模板。改动代码无需重新启动server。不须要配置外部server。etc。随着业务和技术的扩展,使用Play的项目更easy整合其它服务。 比如整合监控工具StatsD+Graphite+Grafana+Kamon,Docker化,服务化。

这篇文章我没有介绍怎样启动应用。由于这须要一些开发环境的准备。以及了解SBT的基本使用方法。这些内容我会在下一篇博客介绍。这篇文章相关的代码已经提交到Github。 项目地址。 这个项目整合了Play,Spring。JPA,数据存储使用MySQL和Redis,使用Bootstrap作为页面框架,能够作为脚手架项目给有兴趣的朋友进行研究。

除此之外。感兴趣的朋友还能够下载Typesafe Reactive Platform进行学习。

这这上面有非常多关于Play,Akka的项目模板, 而且你能够通过浏览器查看编辑这些代码,还能够直接执行。

另外要进一步学习能够读这本书,网上有电子版的。

下篇我会介绍怎样搭建开发环境,以及怎样调试应用。掌握了之后,你会发现开发和调试过程原来还能这样直观和简单!

转载于:https://www.cnblogs.com/ljbguanli/p/7002120.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值