##  头行保存
           
           
           
头行保存在实用中经常用到，头行保存指的是头表和行表(一对多)的数据同时插入，如中途失败则这次事务不进行提交。

假设我们有两张表 


**hls_person（头表）**  

| pid | name | age |
|:---: | :---: | :---: |
| 1 | 张三 | 21  |
| 2 | 李四| 36 |  


**hls_hobby（行表）**  


| hid | pid | hobby | cause |
|:---: | :---: | :---: | :---:|
| 1 | 1 | 篮球  |因为篮球好玩|
| 2 | 1| 游泳 |  因为游泳好玩|
| 3 | 1 |动画片 | 最爱看大头儿子|
| 4 | 2 |画画| 因为画画好玩|


需求是新增一个人的信息以及它的爱好（向hls_person和hls_hobby表新增记录）

下面是一个头行保存的列子：

#### 准备工作
1. 建表，主键默认自增长
2. 搭建数据模型，根据表建立相应的DTO，即HlsPerson.java和HlsHobby.java，注意HlsPerson.java中应存在List \<HlsHobby\> hlsHobbyList属性。
3. 搭建控制层，建立controller
4. 搭建业务逻辑，建立service及其实现类。
5. 搭建数据持久化层，新建mapper.java和mapper.xml

>- 先建立结构，在具体实现功能，以免疏漏。




#### 前台

```javascript

<script><![CDATA[
//提交数据用到的容器
    var viewModel = kendo.observable({
        isEnabled: true,
        data: {},
        mySubmit: function (e) {
            if (e) {
                e.preventDefault();
            }
            Hap.submitForm({
                url: '${base.contextPath}/hls/test/hdLnSave',
                formModel: viewModel.data,
                asArray: false,
                grid: {
                    "hlsHobbyList": $("#grid")

                },
                success: function () {
                    viewModel.showInfoDialog("保存成功!");
                    $('#grid').data('kendoGrid').dataSource.page(1);
                },
                error:function(){
                    viewModel.showInfoDialog("保存失败!");
                }
            });

        },
        showInfoDialog: function(pMessage)
        {
            kendo.ui.showInfoDialog({
                title: $l('提示'),
                message: pMessage
            });
        }


    });

//请求处理方法
function parameterMap(options, type){
        if (type !== "read" && options.models) {
            var datas = options.models
            if (type == 'create' || type == 'update') {
                datas = options.models.map(function(data) {
                    data['__status'] = (type == 'create' ? 'add' : 'update');
                    return data;
                })
            }
            return kendo.stringify(datas);
        } else if (type === "read") {
            var map = {};
            map.page = options.page || 1;
            map.pagesize = options.pageSize|| 5;
            return kendo.stringify(map);
        }
}


//属性列是否可编辑
function dsEditable(field){
    return true;
}
]]></script>
```

```xml
<!--头-->
<h:hlsForm title="个人信息" width="100%">
    <h:hlsHBox>
         <h:hlsMaskedTextBox name="name" id="name" bind="enabled: isEnabled, value:data.name" colspan="3" prompt="姓名:" required="true" style="width:100%;"/>
         <h:hlsMaskedTextBox name="age" id="age" bind="enabled: isEnabled, value:data.age" colspan="3" prompt="年龄:" required="true" style="width:100%;"/>
    </h:hlsHBox>
</h:hlsForm>


<!--行-->
<h:dataSource id="dataSource" batch="true" pageSize="10" serverPaging="true">
      <h:transport parameterMap="parameterMap">
            <h:read url="${base.contextPath}/hls/test/ln/query" type="GET" dataType="json"/>
            <h:destroy url="${base.contextPath}/hls/test/ln/remove" type="POST" contentType="application/json" />
      </h:transport>
      <h:schema data="rows" total="total" errors="schemaError">
        <h:model id="pid" editable="dsEditable">
           <h:fields>
           </h:fields>
        </h:model>
      </h:schema>
    </h:dataSource>
      <h:hlsGridBox hlsGridId="grid" hlsBtnType="add">
	    <h:hlsGrid id="grid" dataSource="dataSource" selectable="" hlsBtnType="ADD,DELETE" editable="true" height="240">
	      <h:pageable pageSizes="5,10,20,50" buttonCount="2" refresh="true">
	      </h:pageable>
	      <h:columns>
	          <h:column field="hobby" title='爱好' width="150">
		      <h:headerAttributes style="text-align:center"/>
	              <h:attributes style="text-align:center"/>
	          </h:column>
	           <h:column field="cause" title='原因'>
		      <h:headerAttributes style="text-align:center"/>
	              <h:attributes style="text-align:center"/>
	          </h:column>   
	      </h:columns>
	    </h:hlsGrid>
     </h:hlsGridBox>
     
    <!--按钮-->
    <h:hlsToolBar>	 
        <h:hlsButton click="viewModel.mySubmit" text="保存"></h:hlsButton>
    </h:hlsToolBar> 
```
      
#### 后台
保存按钮注册了点了事件，点击保存将数据提交到'${base.contextPath}/hls/test/hdLnSave'

> 在后端controller通过定义	@RequestMapping("/hls/test/hdLnSave") 注解映射到对应的url,捕获到对应请求。


后端收到前台传过来的数据，调用service层对数据进行持久化。  

HlsTESTController.java
```java
@Controller
public class HlsTESTController extends BaseController {
	
	@Autowired
	private HlsPersonService hlsPersonService;
	@Autowired
	private HlsHobbyService hlsHobbyService;
	
	@RequestMapping(value = "/hls/test/query")
	@ResponseBody
	public ResponseData hdQuery(final HlsPerson hlsPerson, @RequestParam(defaultValue = DEFAULT_PAGE) final int page,
							  @RequestParam(defaultValue = DEFAULT_PAGE_SIZE) final int pagesize, final HttpServletRequest request) {
		IRequest requestContext = createRequestContext(request);
		return new ResponseData(hlsPersonService.select(requestContext,hlsPerson,page,pagesize));
	}


	@RequestMapping(value="/hls/test/hdLnSave",method=RequestMethod.POST)
	@ResponseBody
	public ResponseData hdLnSave(HttpServletRequest request,@RequestBody HlsPerson hlsPerson, @RequestParam(defaultValue = DEFAULT_PAGE) final int page,
							 @RequestParam(defaultValue = DEFAULT_PAGE_SIZE) final int pagesize){
		IRequest iRequest = createRequestContext(request);
		hlsPersonService.batchChildUpdate(iRequest, hlsPerson);
		return new ResponseData();
	}



	@RequestMapping(value = "/hls/test/ln/query")
	@ResponseBody
	public ResponseData lnQuery(final HlsHobby hlsHobby, @RequestParam(defaultValue = DEFAULT_PAGE) final int page,
							  @RequestParam(defaultValue = DEFAULT_PAGE_SIZE) final int pagesize, final HttpServletRequest request) {
		IRequest requestContext = createRequestContext(request);
		return new ResponseData(hlsHobbyService.select(requestContext,hlsHobby,page,pagesize));
	}


	@RequestMapping(value="/hls/test/ln/remove",method=RequestMethod.POST)
	@ResponseBody
	public ResponseData delete(HttpServletRequest request, @RequestBody List<HlsHobby> hlsHobbyList) {
		hlsHobbyService.batchDelete(hlsHobbyList);
		return new ResponseData();
	}
}


```
> **提示：**
>* 通常头行结构还会涉及对头行数据的删和查，根据需要在controller中加入入口。                                                                                    
>* bathUpdate会根据传入的List对象中单个对象的属性值__status判断，如为update即做更新操作，add为插入操作, __status的值由前端paramter函数决定。  
>* 这里的select()、batchDelete()和batchUpdate()由相应的service继承自IBaseService，自己无需实现，类似方法还有许多，请自行练习使用。 




HlsPersonService.java
```java
public interface HlsPersonService extends IBaseService<HlsPerson>,ProxySelf<HlsPersonService>{
    void  batchChildUpdate(IRequest iRequest, HlsPerson hlsPerson);
}
```




接口实现类HlsPersonServiceImpl定义了batchChildUpdate方法:  

HlsPersonServiceImpl.java
```java
@Service
@Transactional
public class HlsPersonServiceImpl extends BaseServiceImpl<HlsPerson> implements HlsPersonService {

    @Autowired
    private HlsHobbyService hlsHobbyService;

    @Override
    public void batchChildUpdate(IRequest iRequest, HlsPerson hlsPerson) {
        if(hlsPerson != null ){
            hlsPerson.set__status("add");
            List<HlsPerson> tempHlsPerson = new ArrayList<HlsPerson>();
            tempHlsPerson.add(hlsPerson);
            self().batchUpdate(iRequest,tempHlsPerson);
            Long pid = hlsPerson.getPid();
            List<HlsHobby> hlsHobbyList = hlsPerson.getHlsHobbyList();
            for(HlsHobby hh : hlsHobbyList){
                hh.setPid(pid);
            }
            hlsHobbyService.batchUpdate(iRequest,hlsHobbyList);
        }
    }
}







