<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Marshal&#039;s Blog &#187; groovy</title>
	<atom:link href="http://marshal.easymorse.com/archives/tag/groovy/feed" rel="self" type="application/rss+xml" />
	<link>http://marshal.easymorse.com</link>
	<description>It&#039;s swap of marshal&#039;s memory.</description>
	<lastBuildDate>Mon, 30 Jan 2012 07:03:45 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Grails实现zip文件上传及加入文件方案</title>
		<link>http://marshal.easymorse.com/archives/4522?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=grails%25e5%25ae%259e%25e7%258e%25b0zip%25e6%2596%2587%25e4%25bb%25b6%25e4%25b8%258a%25e4%25bc%25a0%25e5%258f%258a%25e5%258a%25a0%25e5%2585%25a5%25e6%2596%2587%25e4%25bb%25b6%25e6%2596%25b9%25e6%25a1%2588</link>
		<comments>http://marshal.easymorse.com/archives/4522#comments</comments>
		<pubDate>Tue, 19 Jul 2011 02:54:07 +0000</pubDate>
		<dc:creator>Marshal</dc:creator>
				<category><![CDATA[计算机技术]]></category>
		<category><![CDATA[grails]]></category>
		<category><![CDATA[grails file upload]]></category>
		<category><![CDATA[grails MarkupBuilder]]></category>
		<category><![CDATA[grails thread]]></category>
		<category><![CDATA[grails xml]]></category>
		<category><![CDATA[grails zip]]></category>
		<category><![CDATA[groovy]]></category>
		<category><![CDATA[java]]></category>

		<guid isPermaLink="false">http://marshal.easymorse.com/archives/4522</guid>
		<description><![CDATA[使用Grails和Groovy，实现了这样的需求： zip文件上传 zip文件上传后，在zip文件中加入自定义文本文件 在Grails实现复杂的数据录入上实现的本示例。示例如图： &#160; 准备工作：文件上传 首先说一下文件上传，在Grails中，借助Spring MVC的底层支持，实现还是很容易的。 视图： &#60;body&#62; &#60;div style=&#34;margin-left: 15px;&#34;&#62; &#60;g:message code=&#34;${flash.message}&#34; /&#62; &#60;g:form action=&#34;uploadFile&#34; method=&#34;post&#34; enctype=&#34;multipart/form-data&#34;&#62; &#60;input type=&#34;file&#34; name=&#34;myFile&#34; /&#62; &#60;input type=&#34;submit&#34; value=&#34;上传&#34; /&#62; &#60;input type=&#34;hidden&#34; name=&#34;id&#34; value=&#34;1&#34;/&#62; &#60;/g:form&#62; &#60;/div&#62; &#60;/body&#62; &#160; 在这里hidden了一个参数，是用于服务器端使用的。 在服务器端获取文件对象和id： def uploadFile={ def id=params.id def file=request.getFile(&#34;myFile&#34;) 下面的问题是： 如何异步的处理zip文件，同步处理是不可能的，那样浏览器端可能长期得不到响应 怎样向zip文件中加入文本文件 怎样动态生成文本文件，本例中是xml文件 &#160; 异步并发的实现 先说异步问题，我希望尽量回避线程，简化开发，这样可以让具体实现人员比较容易上手。这里使用了java的并发api，可参见这里[cref 3801]。 写法类似这样： class BookController [...]]]></description>
			<content:encoded><![CDATA[<p>使用Grails和Groovy，实现了这样的需求：</p>
<ul>
<li>zip文件上传</li>
<li>zip文件上传后，在zip文件中加入自定义文本文件</li>
</ul>
<p>在<a href="http://marshal.easymorse.com/archives/4470" title="Grails实现复杂的数据录入">Grails实现复杂的数据录入</a>上实现的本示例。示例如图：</p>
<p><a href="http://marshal.easymorse.com/wp-content/uploads/2011/07/image26.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://marshal.easymorse.com/wp-content/uploads/2011/07/image_thumb26.png" width="244" height="147" /></a></p>
<p>&#160;</p>
<h3>准备工作：文件上传</h3>
<p>首先说一下文件上传，在Grails中，借助Spring MVC的底层支持，实现还是很容易的。</p>
<p>视图：</p>
<blockquote><p>&lt;body&gt;     <br />&lt;div style=&quot;margin-left: 15px;&quot;&gt;      <br />&lt;g:message code=&quot;${flash.message}&quot; /&gt;      <br />&lt;g:form action=&quot;uploadFile&quot; method=&quot;post&quot; enctype=&quot;multipart/form-data&quot;&gt;      <br />&lt;input type=&quot;file&quot; name=&quot;myFile&quot; /&gt;      <br />&lt;input type=&quot;submit&quot; value=&quot;上传&quot; /&gt;      <br />&lt;input type=&quot;hidden&quot; name=&quot;id&quot; value=&quot;1&quot;/&gt;      <br />&lt;/g:form&gt;      <br />&lt;/div&gt;      <br />&lt;/body&gt;</p>
<p>&#160;</p>
</blockquote>
<p>在这里hidden了一个参数，是用于服务器端使用的。</p>
<p><span id="more-4522"></span>
<p>在服务器端获取文件对象和id：</p>
<blockquote><p>def uploadFile={     <br />def id=params.id      <br />def file=request.getFile(&quot;myFile&quot;)</p>
</blockquote>
<p>下面的问题是：</p>
<ul>
<li>如何异步的处理zip文件，同步处理是不可能的，那样浏览器端可能长期得不到响应</li>
<li>怎样向zip文件中加入文本文件</li>
<li>怎样动态生成文本文件，本例中是xml文件</li>
</ul>
<p>&#160;</p>
<h3>异步并发的实现</h3>
<p>先说异步问题，我希望尽量回避线程，简化开发，这样可以让具体实现人员比较容易上手。这里使用了java的并发api，可参见这里[cref 3801]。</p>
<p>写法类似这样：</p>
<blockquote><p>class BookController {</p>
<p>&#160;&#160;&#160; ExecutorService executorService=Executors.newFixedThreadPool(2)</p>
<p>&#160;</p>
</blockquote>
<p>创建了个实例变量，允许2个并发。</p>
<p>可这样调用：</p>
<blockquote><p>executorService.submit(new Runnable(){     <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; public void run() {</p>
<p>&#160;</p>
</blockquote>
<p>&#160;</p>
<h3>如何向zip文件中加入文件</h3>
<p>时间原因，没时间看apache commons compress等api是否能实现这个功能了。直接招呼java标准zip的api了。</p>
<p>基本思路是创建新的zip文件，将原zip文件中的各个entry加入到新的zip文件输出流中，最后，将自己的文本文件加入。</p>
<p>这里补充一句，从Grails中得到的file对象，生命周期应该是request作用域的。即，当上传文件请求到达服务器端后，在临时目录创建文件保存请求中的文件流。这个文件不可能长期保存，因为那样系统可能会因为文件增长瘫痪的。一般是通过一个Filter在生成响应发送给浏览器后删除本地的临时文件。</p>
<p>这样的话，异步线程的生命周期肯定会超过这个生命周期，因此要在request作用域内把该临时文件先复制到另外的目录下：</p>
<blockquote><p>def path=request.session.servletContext.getRealPath(&quot;/WEB-INF&quot;)</p>
<p>def sourceFile=new File(&quot;&quot;&quot;${path}/${id}.zip&quot;&quot;&quot;)     <br />def distFile=new File(&quot;&quot;&quot;${path}/${id}-dist.zip&quot;&quot;&quot;)      </p>
<p><em><strong>file.transferTo(sourceFile)</strong></em>      </p>
</blockquote>
<p>下面是完整的生成新zip文件，并且加入文本文件的代码：</p>
<blockquote><p>executorService.submit(new Runnable(){     <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; public void run() {      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ZipFile zipFile=new ZipFile(sourceFile)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ZipOutputStream outputStream=new ZipOutputStream(new FileOutputStream(distFile))      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Enumeration entries=zipFile.entries()      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; while(entries.hasMoreElements()){      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ZipEntry entry=entries.nextElement()      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; entry=new ZipEntry(entry.name)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; println entry      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; outputStream.putNextEntry(entry)</p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; if(!entry.isDirectory()){     <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; InputStream inputStream=zipFile.getInputStream(entry)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; byte[] buffer=new byte[1024*1024]      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; for(int i=inputStream.read(buffer);i!=-1;i=inputStream.read(buffer)){      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; outputStream.write(buffer,0,i)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; inputStream.close()      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }</p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; outputStream.closeEntry()     <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; zipFile.close()</p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ZipEntry attachEntry=new ZipEntry(&quot;book.plist&quot;)     <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; outputStream.putNextEntry(attachEntry)</p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; InputStream inputStream=new FileInputStream(attachedFile)</p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <em><strong> outputStream.write(getResults(id).getBytes())</strong></em></p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; inputStream.close()</p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; outputStream.closeEntry()</p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; outputStream.close()     <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; println &quot;&gt;&gt;&gt;&gt; zip create ok.&quot;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; })</p>
</blockquote>
<p>其中黑体中调用的是解决动态生成文本的内容，这里假定write方面里面的参数是String即可，是String.getBytes()。</p>
<p>&#160;</p>
<h3>动态生成自定义xml文本</h3>
<p>这里可参阅<a href="http://marshal.easymorse.com/archives/1377" title="groovy生成xml">groovy生成xml</a>，这里使用到了MarkupBuilder。</p>
<p>代码如下：</p>
<blockquote><p>&#160;&#160;&#160; def getResults(def bookId){     <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; def writer=new StringWriter()      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; def xmlResults=new MarkupBuilder(writer)</p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160; xmlResults.plist(version:1.0){     <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; dict{      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; key &#8216;key&#8217;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; string &#8217;12&#8242;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; }</p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160; def content=&quot;&quot;&quot;&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;     <br />&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;<a href="http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;">http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;</a>&gt;      <br />${writer}      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; &quot;&quot;&quot;</p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160; return content     <br />&#160;&#160;&#160; }</p>
<p>&#160;</p>
</blockquote>
<p>生成的文本类似这样：</p>
<blockquote><p>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;     <br />&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;<a href="http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;">http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;</a>&gt;      <br />&lt;plist version=&#8217;1.0&#8242;&gt;      <br />&#160; &lt;dict&gt;      <br />&#160;&#160;&#160; &lt;key&gt;key&lt;/key&gt;      <br />&#160;&#160;&#160; &lt;string&gt;12&lt;/string&gt;      <br />&#160; &lt;/dict&gt;      <br />&lt;/plist&gt;</p>
</blockquote>
<p>完整源代码见：</p>
<blockquote><p><a title="http://easymorse.googlecode.com/svn/tags/BookProto-0.5/" href="http://easymorse.googlecode.com/svn/tags/BookProto-0.5/">http://easymorse.googlecode.com/svn/tags/BookProto-0.5/</a></p>
</blockquote>
]]></content:encoded>
			<wfw:commentRss>http://marshal.easymorse.com/archives/4522/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Grails图书示例纠正bug</title>
		<link>http://marshal.easymorse.com/archives/4494?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=grails%25e5%259b%25be%25e4%25b9%25a6%25e7%25a4%25ba%25e4%25be%258b%25e7%25ba%25a0%25e6%25ad%25a3bug</link>
		<comments>http://marshal.easymorse.com/archives/4494#comments</comments>
		<pubDate>Tue, 12 Jul 2011 04:39:36 +0000</pubDate>
		<dc:creator>Marshal</dc:creator>
				<category><![CDATA[计算机技术]]></category>
		<category><![CDATA[grails]]></category>
		<category><![CDATA[grails project demo]]></category>
		<category><![CDATA[groovy]]></category>
		<category><![CDATA[web app]]></category>

		<guid isPermaLink="false">http://marshal.easymorse.com/archives/4494</guid>
		<description><![CDATA[在Grails实现复杂的数据录入中有一个bug，当新建图书，选择相关图书时，如果选择了相关图书，再次选择，还是出现全部已保存图书。 改动代码见下面： //对话框获取符合条件的相关图书 def getNotRelatedBooks={ &#160;&#160;&#160; def bookId=params.id &#160;&#160;&#160; def relatedIds=request.getParameterValues(&#8216;related_id&#8217;) &#160;&#160;&#160; def notRelatedBooks=[] &#160;&#160;&#160; def hql=&#8217;from Book b &#8216; &#160;&#160;&#160; &#160;&#160;&#160; /* &#160;&#160;&#160; if(params.id){ &#160;&#160;&#160;&#160;&#160;&#160;&#160; def relatedIds=request.getParameterValues(&#8216;related_id&#8217;) &#160;&#160;&#160;&#160;&#160;&#160;&#160; hql+=&#8217; where b.id not in (&#8216; &#160;&#160;&#160;&#160;&#160;&#160;&#160; if(relatedIds &#38;&#38; relatedIds.size()&#62;0){ &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; relatedIds.each { relatedId-&#62; hql&#60;&#60;=&#34;&#34;&#34;&#8217;${relatedId}&#8217;,&#34;&#34;&#34; } &#160;&#160;&#160;&#160;&#160;&#160;&#160; } &#160;&#160;&#160;&#160;&#160;&#160;&#160; hql&#60;&#60;=&#34;&#34;&#34;&#8217;${bookId}&#8217;)&#34;&#34;&#34; &#160;&#160;&#160; } &#160;&#160;&#160; */ &#160;&#160;&#160; &#160;&#160;&#160; [...]]]></description>
			<content:encoded><![CDATA[<p>在<a href="http://marshal.easymorse.com/archives/4470" title="Grails实现复杂的数据录入">Grails实现复杂的数据录入</a>中有一个bug，当新建图书，选择相关图书时，如果选择了相关图书，再次选择，还是出现全部已保存图书。</p>
<p>改动代码见下面：</p>
<p><span id="more-4494"></span><br />
<blockquote>
<p>//对话框获取符合条件的相关图书     <br />def getNotRelatedBooks={      <br />&#160;&#160;&#160; def bookId=params.id      <br />&#160;&#160;&#160; def relatedIds=request.getParameterValues(&#8216;related_id&#8217;)      <br />&#160;&#160;&#160; def notRelatedBooks=[]</p>
<p>&#160;&#160;&#160; def hql=&#8217;from Book b &#8216;     <br />&#160;&#160;&#160; <br />&#160;&#160;&#160; /*      <br />&#160;&#160;&#160; if(params.id){      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; def relatedIds=request.getParameterValues(&#8216;related_id&#8217;)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; hql+=&#8217; where b.id not in (&#8216;</p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160; if(relatedIds &amp;&amp; relatedIds.size()&gt;0){     <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; relatedIds.each { relatedId-&gt; hql&lt;&lt;=&quot;&quot;&quot;&#8217;${relatedId}&#8217;,&quot;&quot;&quot; }      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; }</p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160; hql&lt;&lt;=&quot;&quot;&quot;&#8217;${bookId}&#8217;)&quot;&quot;&quot;     <br />&#160;&#160;&#160; }      <br />&#160;&#160;&#160; */      <br />&#160;&#160;&#160; <br />&#160;&#160;&#160; if(bookId || relatedIds){      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; def idList=[]      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; if(bookId){      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; idList.add(bookId)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; }      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; if(relatedIds){      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; idList.addAll(relatedIds)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; }      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; def notInIdsString=&quot;&quot;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; for(int i=0;i&lt;idList.size();i++){      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; notInIdsString+=&quot;&quot;&quot;&#8217;${idList[i]}&#8217;&quot;&quot;&quot;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; if(i&lt;idList.size()-1){      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; notInIdsString+=&quot;,&quot;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; }      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; hql+=&quot;&quot;&quot; where b.id not in ( ${notInIdsString} )&quot;&quot;&quot;      <br />&#160;&#160;&#160; }      <br />&#160;&#160;&#160; <br />&#160;&#160;&#160; <br />&#160;&#160;&#160; notRelatedBooks=Book.findAll(hql)</p>
<p>&#160;&#160;&#160; render(contentType:&quot;text/json&quot;){     <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; array{      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; for(b in notRelatedBooks){      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; book id:b.id, name:b.name      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; }      <br />&#160;&#160;&#160; }      <br />}</p>
<p>&#160;</p>
</blockquote>
<p><font style="background-color: #f4f4f4"></font>其中注释掉的代码块，是错误的部分。</p>
<p>完整的项目代码，见：</p>
<blockquote><p><font style="background-color: #ffffff"></font><a title="http://easymorse.googlecode.com/svn/tags/BookProto-0.4/" href="http://easymorse.googlecode.com/svn/tags/BookProto-0.4/">http://easymorse.googlecode.com/svn/tags/BookProto-0.4/</a></p>
</blockquote>
]]></content:encoded>
			<wfw:commentRss>http://marshal.easymorse.com/archives/4494/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Grails编写断点续传服务器端</title>
		<link>http://marshal.easymorse.com/archives/4492?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=grails%25e7%25bc%2596%25e5%2586%2599%25e6%2596%25ad%25e7%2582%25b9%25e7%25bb%25ad%25e4%25bc%25a0%25e6%259c%258d%25e5%258a%25a1%25e5%2599%25a8%25e7%25ab%25af</link>
		<comments>http://marshal.easymorse.com/archives/4492#comments</comments>
		<pubDate>Mon, 11 Jul 2011 15:18:47 +0000</pubDate>
		<dc:creator>Marshal</dc:creator>
				<category><![CDATA[计算机技术]]></category>
		<category><![CDATA[grails]]></category>
		<category><![CDATA[grails http]]></category>
		<category><![CDATA[groovy]]></category>
		<category><![CDATA[http]]></category>
		<category><![CDATA[web app]]></category>

		<guid isPermaLink="false">http://marshal.easymorse.com/archives/4492</guid>
		<description><![CDATA[在测试Tomcat对断点续传的支持中发现，Tomcat是支持静态内容的断点续传的，但是，它无法支持动态内容的断点续传。比如我有一些客户定制化数据，只有登录的用户才能下载。如果放在静态目录下，安全就形同虚设。 因此，在Grails中一般要放在WEB-INF目录的子目录下。比如： 这样的内容需要自己创建文件，然后输出到response的输出流上。因此断点续传必须自己实现。 还是在Grails实现复杂的数据录入基础上做的改动： class BookController { &#160;&#160;&#160; def book &#160;&#160;&#160; &#160;&#160;&#160; def image={ &#160;&#160;&#160;&#160;&#160;&#160;&#160; def imageFile=new File(request.session.getServletContext().getRealPath(&#34;/WEB-INF/images/grails_logo.png&#34;)) &#160;&#160;&#160;&#160;&#160;&#160;&#160; def range=request.getHeader(&#8216;Range&#8217;) &#160;&#160;&#160;&#160;&#160;&#160;&#160; int rangeValue=0 &#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;&#160;&#160;&#160;&#160;&#160;&#160; if(range){ &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; rangeValue=Integer.parseInt(range.substring (6, range.length()-1)) &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; response.setStatus(206) &#160;&#160;&#160;&#160;&#160;&#160;&#160; } &#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;&#160;&#160;&#160;&#160;&#160;&#160; def imageData=null &#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;&#160;&#160;&#160;&#160;&#160;&#160; if(range){ &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; def inputStream=new FileInputStream(imageFile) &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; imageData=new byte[imageFile.length()-rangeValue] &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; inputStream.skip(rangeValue) &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; inputStream.read(imageData, 0, imageData.length) [...]]]></description>
			<content:encoded><![CDATA[<p>在<a href="http://marshal.easymorse.com/archives/4488" title="测试Tomcat对断点续传的支持">测试Tomcat对断点续传的支持</a>中发现，Tomcat是支持静态内容的断点续传的，但是，它无法支持动态内容的断点续传。比如我有一些客户定制化数据，只有登录的用户才能下载。如果放在静态目录下，安全就形同虚设。</p>
<p>因此，在Grails中一般要放在WEB-INF目录的子目录下。比如：</p>
<p><a href="http://marshal.easymorse.com/wp-content/uploads/2011/07/image20.png"><img style="background-image: none; border-right-width: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://marshal.easymorse.com/wp-content/uploads/2011/07/image_thumb20.png" width="233" height="244" /></a></p>
<p>这样的内容需要自己创建文件，然后输出到response的输出流上。因此断点续传必须自己实现。</p>
<p><span id="more-4492"></span>
<p>还是在<a href="http://marshal.easymorse.com/archives/4470" title="Grails实现复杂的数据录入">Grails实现复杂的数据录入</a>基础上做的改动：</p>
<blockquote><p>class BookController {</p>
<p>&#160;&#160;&#160; def book      <br />&#160;&#160;&#160; <br />&#160;&#160;&#160; def image={       <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; def imageFile=new File(request.session.getServletContext().getRealPath(&quot;/WEB-INF/images/grails_logo.png&quot;))       <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; def range=request.getHeader(&#8216;Range&#8217;)       <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; int rangeValue=0       <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; if(range){       <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; rangeValue=Integer.parseInt(range.substring (6, range.length()-1))       <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; response.setStatus(206)       <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; }       <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; def imageData=null       <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; if(range){       <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; def inputStream=new FileInputStream(imageFile)       <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; imageData=new byte[imageFile.length()-rangeValue]       <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; inputStream.skip(rangeValue)       <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; inputStream.read(imageData, 0, imageData.length)       <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; }else{       <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; imageData=imageFile.readBytes()       <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; }       <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; //response.setHeader(&quot;Content-disposition&quot;, &quot;attachment; filename=grails.png&quot;)       <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; response.contentType =&#8217;image/jpeg&#8217;       <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; response.addHeader &quot;Content-Length&quot;, &quot;&quot;+(imageData.length)       <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; response.outputStream&lt;&lt;imageData       <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; response.outputStream.flush()       <br />&#160;&#160;&#160; }</p>
<p>&#160;</p>
</blockquote>
<p>用<a href="http://marshal.easymorse.com/archives/4488" title="测试Tomcat对断点续传的支持">测试Tomcat对断点续传的支持</a>中的java程序测试，结果分别是：</p>
<blockquote><p>content length: 10172      <br />status code:200       <br />1:137       <br />2:80       <br />3:78       <br />4:71       <br />5:13       <br />6:10       <br />7:26       <br />8:10       <br />9:0       <br />10:0       <br />11:0</p>
<p>content length: 10169      <br />status code:206       <br />1:71       <br />2:13       <br />3:10       <br />4:26       <br />5:10       <br />6:0       <br />7:0       <br />8:0       <br />9:13       <br />10:73       <br />11:72</p>
<p>&#160;</p>
</blockquote>
<p>和<a href="http://marshal.easymorse.com/archives/4488" title="测试Tomcat对断点续传的支持">测试Tomcat对断点续传的支持</a>一致，说明实现是成功的。</p>
<p>完整的源代码见：</p>
<blockquote><p><a title="http://easymorse.googlecode.com/svn/tags/BookProto-0.3/" href="http://easymorse.googlecode.com/svn/tags/BookProto-0.3/">http://easymorse.googlecode.com/svn/tags/BookProto-0.3/</a></p>
</blockquote>
]]></content:encoded>
			<wfw:commentRss>http://marshal.easymorse.com/archives/4492/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>使用Grails flash作用域</title>
		<link>http://marshal.easymorse.com/archives/4475?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=%25e4%25bd%25bf%25e7%2594%25a8grails-flash%25e4%25bd%259c%25e7%2594%25a8%25e5%259f%259f</link>
		<comments>http://marshal.easymorse.com/archives/4475#comments</comments>
		<pubDate>Mon, 11 Jul 2011 12:18:54 +0000</pubDate>
		<dc:creator>Marshal</dc:creator>
				<category><![CDATA[计算机技术]]></category>
		<category><![CDATA[grails]]></category>
		<category><![CDATA[grails flash scope]]></category>
		<category><![CDATA[groovy]]></category>

		<guid isPermaLink="false">http://marshal.easymorse.com/archives/4475</guid>
		<description><![CDATA[Grails引入了flash作用域的概念。这是很多高级web框架都引入的概念了。在Servlet中没有这个作用域。如果要处理这样的场景，比如更新（新建）表单提交，又重定向到表单页面，用户看不到什么变化。 比如这个界面（详见Grails实现复杂的数据录入），保存前后，都是相同的界面和相同的链接。无法让客户看出是否真的更新了。 以前的解决办法，一般有两条： 写个javascript alert，提示客户 在页面中增加提示 那么问题是，如何让这个页面知道是保存过了呢？也有两个办法： 在url中增加内容，比如：http://localhost:8080/BookProto/book/edit/9?message=1，重定向回来就可以根据这个message参数显示了 或者，可以在session作用域存放一个标记，等重定向后取出并删除 其实flash作用域就是基于session作用域做的，但是更方便。 首先在save方法中增加： def save={ &#160;&#160;&#160; book=new Book(params) &#160;&#160;&#160; book.save() &#160;&#160;&#160; &#160;&#160;&#160; flash.message=&#34;&#34;&#34;图书（${book.name}）已保存。&#34;&#34;&#34; &#160;&#160;&#160; redirect(action:&#8217;edit&#8217;,id:book.id) } 然后，在页面中可通过标签取出来： &#60;body&#62; &#160;&#160;&#160; &#60;div class=&#34;content&#34; style=&#34;margin-left: 20px;&#34;&#62; &#160;&#160;&#160;&#160;&#160;&#160; &#60;g:message code=&#34;${flash.message}&#34; /&#62; &#160;&#160;&#160;&#160;&#160;&#160;&#160; &#60;g:form&#160; id=&#34;bookForm&#34; url=&#34;[controller:'book',action:'save']&#34;&#62; 这样当保存后重定向就是下面的样子了： 完整的源代码见： http://easymorse.googlecode.com/svn/tags/BookProto-0.2/]]></description>
			<content:encoded><![CDATA[<p>Grails引入了flash作用域的概念。这是很多高级web框架都引入的概念了。在Servlet中没有这个作用域。如果要处理这样的场景，比如更新（新建）表单提交，又重定向到表单页面，用户看不到什么变化。</p>
<p><a href="http://marshal.easymorse.com/wp-content/uploads/2011/07/image14.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://marshal.easymorse.com/wp-content/uploads/2011/07/image_thumb14.png" width="244" height="139" /></a></p>
<p>比如这个界面（详见<a href="http://marshal.easymorse.com/archives/4470" title="Grails实现复杂的数据录入">Grails实现复杂的数据录入</a>），保存前后，都是相同的界面和相同的链接。无法让客户看出是否真的更新了。</p>
<p><span id="more-4475"></span>
<p>以前的解决办法，一般有两条：</p>
<ul>
<li>写个javascript alert，提示客户</li>
<li>在页面中增加提示</li>
</ul>
<p>那么问题是，如何让这个页面知道是保存过了呢？也有两个办法：</p>
<ul>
<li>在url中增加内容，比如：<a title="http://localhost:8080/BookProto/book/edit/9" href="http://localhost:8080/BookProto/book/edit/9?message=1">http://localhost:8080/BookProto/book/edit/9?message=1</a>，重定向回来就可以根据这个message参数显示了</li>
<li>或者，可以在session作用域存放一个标记，等重定向后取出并删除</li>
</ul>
<p>其实flash作用域就是基于session作用域做的，但是更方便。</p>
<p>首先在save方法中增加：</p>
<blockquote><p>def save={     <br />&#160;&#160;&#160; book=new Book(params)      <br />&#160;&#160;&#160; book.save()      <br />&#160;&#160;&#160; <br />&#160;&#160;&#160; <strong><em>flash.message=&quot;&quot;&quot;图书（${book.name}）已保存。&quot;&quot;&quot;         <br /></em></strong>&#160;&#160;&#160; redirect(action:&#8217;edit&#8217;,id:book.id)      <br />}</p>
</blockquote>
<p><font style="background-color: #f4f4f4"></font>然后，在页面中可通过标签取出来：</p>
<blockquote><p>&lt;body&gt;     <br />&#160;&#160;&#160; &lt;div class=&quot;content&quot; style=&quot;margin-left: 20px;&quot;&gt;      <br />&#160;&#160;&#160;&#160;&#160;&#160; <em><strong> &lt;g:message code=&quot;${flash.message}&quot; /&gt;         <br /></strong></em>&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;g:form&#160; id=&quot;bookForm&quot; url=&quot;[controller:'book',action:'save']&quot;&gt;</p>
</blockquote>
<p><font style="background-color: #f4f4f4"></font>这样当保存后重定向就是下面的样子了：</p>
<p><a href="http://marshal.easymorse.com/wp-content/uploads/2011/07/image15.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://marshal.easymorse.com/wp-content/uploads/2011/07/image_thumb15.png" width="244" height="148" /></a>    </p>
<p>完整的源代码见：</p>
<blockquote><p><a title="http://easymorse.googlecode.com/svn/tags/BookProto-0.2/" href="http://easymorse.googlecode.com/svn/tags/BookProto-0.2/">http://easymorse.googlecode.com/svn/tags/BookProto-0.2/</a></p>
</blockquote>
]]></content:encoded>
			<wfw:commentRss>http://marshal.easymorse.com/archives/4475/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Grails实现复杂的数据录入</title>
		<link>http://marshal.easymorse.com/archives/4470?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=grails%25e5%25ae%259e%25e7%258e%25b0%25e5%25a4%258d%25e6%259d%2582%25e7%259a%2584%25e6%2595%25b0%25e6%258d%25ae%25e5%25bd%2595%25e5%2585%25a5</link>
		<comments>http://marshal.easymorse.com/archives/4470#comments</comments>
		<pubDate>Mon, 11 Jul 2011 11:50:13 +0000</pubDate>
		<dc:creator>Marshal</dc:creator>
				<category><![CDATA[计算机技术]]></category>
		<category><![CDATA[grails]]></category>
		<category><![CDATA[grails gorm]]></category>
		<category><![CDATA[grails jquery]]></category>
		<category><![CDATA[grails json]]></category>
		<category><![CDATA[groovy]]></category>
		<category><![CDATA[web app]]></category>

		<guid isPermaLink="false">http://marshal.easymorse.com/archives/4470</guid>
		<description><![CDATA[本示例由Grails实现备选相关图书修改而来。前者存在一些问题，在本示例中进行了纠正。 首先说一下实现的效果，新建图书： 点击增加按钮，可从图书列表中获取相关图书： 确认后并保存的样子： 下面完整的说一下做的过程。 实体关系 首先是分析图书实体的关系。这里应该是一对多关系： 实体只有一个，是自身的关联关系，代码： class Book { &#160;&#160;&#160; static mapping = { &#160;&#160;&#160;&#160;&#160;&#160;&#160; //id generator:&#8217;uuid.hex&#8217;, params:[separator:'-'] &#160;&#160;&#160; } &#160;&#160;&#160; &#160;&#160;&#160; static hasMany=[relativeBooks:Book] &#160;&#160;&#160; &#160;&#160;&#160; //String id &#160;&#160;&#160; String name &#160;&#160;&#160; List&#60;Book&#62; relativeBooks=[] } &#160; 这里屏蔽了uuid的主键生成（Grails的GORM中使用uuid），是为了测试方便。 为了开发测试方便，编写了加载初始数据代码： class BootStrap { &#160;&#160;&#160; def init = { servletContext -&#62; &#160;&#160;&#160;&#160;&#160;&#160;&#160; environments{ &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; development{ &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; [...]]]></description>
			<content:encoded><![CDATA[<p>本示例由<a href="http://marshal.easymorse.com/archives/4460" title="Grails实现备选相关图书">Grails实现备选相关图书</a>修改而来。前者存在一些问题，在本示例中进行了纠正。</p>
<p>首先说一下实现的效果，新建图书：</p>
<p><a href="http://marshal.easymorse.com/wp-content/uploads/2011/07/image10.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px 8px 0px 4px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://marshal.easymorse.com/wp-content/uploads/2011/07/image_thumb10.png" width="244" height="154" /></a></p>
<p>点击增加按钮，可从图书列表中获取相关图书：</p>
<p><a href="http://marshal.easymorse.com/wp-content/uploads/2011/07/image11.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://marshal.easymorse.com/wp-content/uploads/2011/07/image_thumb11.png" width="244" height="183" /></a></p>
<p><span id="more-4470"></span>
<p>确认后并保存的样子：</p>
<p><a href="http://marshal.easymorse.com/wp-content/uploads/2011/07/image12.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://marshal.easymorse.com/wp-content/uploads/2011/07/image_thumb12.png" width="244" height="163" /></a></p>
<p>下面完整的说一下做的过程。</p>
<h3>实体关系</h3>
<p>首先是分析图书实体的关系。这里应该是一对多关系：</p>
<p><a href="http://marshal.easymorse.com/wp-content/uploads/2011/07/image13.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://marshal.easymorse.com/wp-content/uploads/2011/07/image_thumb13.png" width="244" height="206" /></a></p>
<p>实体只有一个，是自身的关联关系，代码：</p>
<blockquote><p>class Book {     <br />&#160;&#160;&#160; static mapping = {      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; //id generator:&#8217;uuid.hex&#8217;, params:[separator:'-']      <br />&#160;&#160;&#160; }      <br />&#160;&#160;&#160; <br />&#160;&#160;&#160; static hasMany=[relativeBooks:Book]      <br />&#160;&#160;&#160; <br />&#160;&#160;&#160; //String id      <br />&#160;&#160;&#160; String name      <br />&#160;&#160;&#160; List&lt;Book&gt; relativeBooks=[]      <br />}</p>
<p>&#160;</p>
</blockquote>
<p>这里屏蔽了uuid的主键生成（<a href="http://marshal.easymorse.com/archives/4440" title="Grails的GORM中使用uuid">Grails的GORM中使用uuid</a>），是为了测试方便。</p>
<p>为了开发测试方便，编写了加载初始数据代码：</p>
<blockquote><p>class BootStrap {</p>
<p>&#160;&#160;&#160; def init = { servletContext -&gt;     <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; environments{      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; development{      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; def testBook=new Book(name:&quot;中国史学史&quot;)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; testBook.relativeBooks.add(new Book(name:&quot;中国通史&quot;))      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; testBook.relativeBooks.add(new Book(name:&quot;中国农业史&quot;))      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; testBook.save()      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; println &quot;&quot;&quot;test book: ${testBook}&quot;&quot;&quot;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; println &quot;&quot;&quot;test book relative books:${testBook.relativeBooks}&quot;&quot;&quot;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; new Book(name:&quot;中国手工业史&quot;).save()      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; new Book(name:&quot;中国战争史&quot;).save()      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; new Book(name:&quot;中国科学技术史&quot;).save()      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; new Book(name:&quot;中国对外交流史&quot;).save()      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; }      <br />&#160;&#160;&#160; }      <br />&#160;&#160;&#160; def destroy = {      <br />&#160;&#160;&#160; }      <br />}</p>
</blockquote>
<p>详见<a href="http://marshal.easymorse.com/archives/4447" title="Grails开发环境下如何生成模拟测试数据">Grails开发环境下如何生成模拟测试数据</a>。</p>
<p>&#160;</p>
<h3>界面的实现</h3>
<p>界面使用了SiteMesh，并在SiteMesh模板中集成了jQuery，详见<a href="http://marshal.easymorse.com/archives/4452" title="使用Grails布局功能">使用Grails布局功能</a>。</p>
<p>完整的edit.gsp源代码：</p>
<blockquote><p>&lt;html&gt;     <br />&lt;head&gt;      <br />&lt;title&gt;编辑图书&lt;/title&gt;      <br />&lt;meta name=&quot;layout&quot; content=&quot;main&quot;&gt;&lt;/meta&gt;      <br />&lt;script type=&quot;text/javascript&quot;&gt;      <br />&lt;%      <br />//初始化javascript的book对象      <br />if(book){//修改功能：根据controller的book对象生成js对象      <br />&#160;&#160;&#160; out&lt;&lt;&quot;&quot;&quot;var book={id:&#8217;${book.id}&#8217;,name:&#8217;${book.name}&#8217;};\n&quot;&quot;&quot;      <br />&#160;&#160;&#160; out&lt;&lt;&quot;&quot;&quot;book.initRelativeBooks=[];\n&quot;&quot;&quot;      <br />&#160;&#160;&#160; for(b in book.relativeBooks){      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; out&lt;&lt;&quot;&quot;&quot;book.initRelativeBooks.push({id:&#8217;${b.id}&#8217;,name:&#8217;${b.name}&#8217;});\n&quot;&quot;&quot;      <br />&#160;&#160;&#160; }      <br />}else{//新建功能      <br />&#160;&#160;&#160; out&lt;&lt;&quot;&quot;&quot;var book={};&quot;&quot;&quot;      <br />&#160;&#160;&#160; out&lt;&lt;&quot;&quot;&quot;book.initRelativeBooks=[];&quot;&quot;&quot;      <br />&#160;&#160;&#160; out&lt;&lt;&quot;&quot;&quot;book.relativeBooks=[];&quot;&quot;&quot;      <br />}</p>
<p>//slice函数将initRelativeBooks的元素复制给relativeBooks     <br />//在两个变量中存储相关图书元素，是为了点击重置按钮时能够恢复      <br />out&lt;&lt;&quot;book.relativeBooks=book.initRelativeBooks.slice(0);&quot;      <br />%&gt;</p>
<p>//对话框中可选的相关图书数组变量     <br />var chooseRelativeBooks=[];</p>
<p>//刷新相关图书节点     <br />function refreshRelativeBook(){      <br />&#160;&#160;&#160; $(&quot;#relativeBooks&quot;).empty();      <br />&#160;&#160;&#160; for(var i in book.relativeBooks){      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; $(&quot;#relativeBooks&quot;).append(&quot;&lt;div&gt;&quot;+book.relativeBooks[i].name+&quot;&lt;input type=&#8217;button&#8217; value=&#8217;删除&#8217; onclick=&#8217;deleteRelativeBook(&quot;+i+&quot;)&#8217;/&gt;&lt;/div&gt;&quot;);      <br />&#160;&#160;&#160; }      <br />}</p>
<p>//根据数组下标删除相关图书     <br />function deleteRelativeBook(index){      <br />&#160;&#160;&#160; book.relativeBooks.splice(index,1);      <br />&#160;&#160;&#160; refreshRelativeBook();      <br />}</p>
<p>//点击重置按钮执行此方法     <br />function doReset(){      <br />&#160;&#160;&#160; book.relativeBooks=book.initRelativeBooks.slice(0);      <br />&#160;&#160;&#160; refreshRelativeBook();      <br />}</p>
<p>//点击对话框中checkbox框执行此方法     <br />function checkRelativeBook(input){      <br />&#160;&#160;&#160; var b=chooseRelativeBooks[input.value];      <br />&#160;&#160;&#160; b.checked=input.checked;      <br />}</p>
<p>//用于下面方便的使用reset函数     <br />jQuery.fn.reset = function(fn) {      <br />&#160;&#160;&#160; return fn ? this.bind(&quot;reset&quot;, fn) : this.trigger(&quot;reset&quot;);      <br />};</p>
<p>&lt;/script&gt;     <br />&lt;/head&gt;      <br />&lt;body&gt;      <br />&#160;&#160;&#160; &lt;div class=&quot;content&quot; style=&quot;margin-left: 20px;&quot;&gt;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;g:form&#160; id=&quot;bookForm&quot; url=&quot;[controller:'book',action:'save']&quot;&gt;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;div&gt;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 书名：&lt;input id=&quot;bookName&quot; type=&quot;text&quot; name=&quot;name&quot; value=&quot;${book?.name}&quot; /&gt;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;/div&gt;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;p /&gt;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;div&gt;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 相关图书：&lt;input id=&quot;addRelativeBooksButton&quot; type=&quot;button&quot; value=&quot;增加&quot; /&gt;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;/div&gt;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;div id=&quot;relativeBooks&quot;&gt;&lt;/div&gt;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;p /&gt;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;hr width=&quot;250px&quot; style=&quot;margin-top: 10px; margin-bottom: 10px;&quot; /&gt;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;input type=&quot;submit&quot; value=&quot;保存&quot; /&gt; &lt;input type=&quot;reset&quot; value=&quot;重置&quot; /&gt;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;input type=&quot;hidden&quot; name=&quot;id&quot; value=&quot;${book?.id}&quot; /&gt;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;/g:form&gt;      <br />&#160;&#160;&#160; &lt;/div&gt;</p>
<p>&lt;div id=&quot;addRelativeBooksDialog&quot; title=&quot;添加相关图书&quot;&gt;     <br />&#160;&#160;&#160; &lt;div id=&quot;addRelativeBooksContainer&quot;&gt;&lt;/div&gt;      <br />&lt;/div&gt;</p>
<p>&lt;script type=&quot;text/javascript&quot;&gt;     <br />refreshRelativeBook();</p>
<p>//jQuery不直接支持$(&quot;#bookForm&quot;).reset这种写法，jQuery.fn.reset&#8230;后可以这样写     <br />$(&quot;#bookForm&quot;).reset(function(){      <br />&#160;&#160;&#160; doReset();      <br />&#160;&#160;&#160; return true;      <br />});</p>
<p>//创建对话框     <br />$(&quot;#addRelativeBooksDialog&quot;).dialog({autoOpen:false,buttons:{      <br />&#160;&#160;&#160; &quot;确认&quot;:function(){      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; for(var i in chooseRelativeBooks){      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; var b=chooseRelativeBooks[i];      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; if(b.checked){      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; book.relativeBooks.push(b);      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; }      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; refreshRelativeBook();      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; $(this).dialog(&quot;close&quot;);      <br />&#160;&#160;&#160; },      <br />&#160;&#160;&#160; &quot;取消&quot;:function(){      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; $(this).dialog(&quot;close&quot;);      <br />&#160;&#160;&#160; }      <br />}});</p>
<p>//打开对话框     <br />//基本思路是打开对话框，播放loading gif图，ajax回调后去掉loading图，改为相关图书列表      <br />$(&quot;#addRelativeBooksButton&quot;).click(function(){      <br />&#160;&#160;&#160; $(&quot;#addRelativeBooksDialog&quot;).dialog(&quot;open&quot;);      <br />&#160;&#160;&#160; $(&quot;#addRelativeBooksContainer&quot;).empty();      <br />&#160;&#160;&#160; $(&quot;#addRelativeBooksContainer&quot;).append(&#8216;&lt;img alt=&quot;loading &#8230;&quot; src=&quot;${resource(dir:&#8217;images&#8217;,file:&#8217;loading.gif&#8217;)}&quot; style=&quot;margin-left: 120px;margin-top: 20px;&quot;/&gt;&#8217;);      <br />&#160;&#160;&#160; var url=&quot;${createLink(controller:&#8217;book&#8217;,action:&#8217;getNotRelatedBooks&#8217;,id:book?.id)}&quot;</p>
<p>&#160;&#160;&#160; for(var i in book.relativeBooks){     <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; url=url+&quot;&amp;related_id=&quot;+book.relativeBooks[i].id;      <br />&#160;&#160;&#160; }      <br />&#160;&#160;&#160; <br />&#160;&#160;&#160; $.ajax({      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; url:url,      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; success:function(data){      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; $(&quot;#addRelativeBooksContainer&quot;).empty();      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; chooseRelativeBooks=data;</p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; for(var i in chooseRelativeBooks){     <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; $(&quot;#addRelativeBooksContainer&quot;).append(&#8216;&lt;div&gt;&lt;input type=&quot;checkbox&quot; value=&quot;&#8217;+i+&#8217;&quot; onclick=&quot;checkRelativeBook(this)&quot;&gt;&#8217;+chooseRelativeBooks[i].name+&#8217;&lt;/input&gt;&lt;/div&gt;&#8217;);      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; });      <br />});</p>
<p>//提交表单时拦截，将选择了的新相关图书加入     <br />$(&quot;#bookForm&quot;).submit(function(){      <br />&#160;&#160;&#160; for(var i in book.relativeBooks){      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; var b=book.relativeBooks[i];      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; $(&#8216;#bookForm&#8217;).append(&#8216;&lt;input type=&quot;hidden&quot; name=&quot;relativeBooks['+i+'].id&quot; value=&quot;&#8217;+b.id+&#8217;&quot; /&gt;&#8217;);      <br />&#160;&#160;&#160; }      <br />});      <br />&lt;/script&gt;      <br />&lt;/body&gt;      <br />&lt;/html&gt;</p>
<p>&#160;</p>
</blockquote>
<p>注释比较清楚了。这里提个和url有关的事情。资源文件比如图片一般放在wepapp/images目录下，以前jsp是比较麻烦的，gsp的写法：</p>
<blockquote><p>&lt;img alt=&quot;loading &#8230;&quot; src=&quot;${resource(dir:&#8217;images&#8217;,file:&#8217;loading.gif&#8217;)}&quot;</p>
</blockquote>
<p>如果是controller的url，可以类似这样：</p>
<blockquote><p>var url=&quot;${createLink(controller:&#8217;book&#8217;,action:&#8217;getNotRelatedBooks&#8217;,id:book?.id)}&quot;</p>
</blockquote>
<p>如果是form，也可以使用标签：</p>
<blockquote><p>&lt;g:form&#160; id=&quot;bookForm&quot; url=&quot;[controller:'book',action:'save']&quot;&gt;</p>
</blockquote>
<p>下面说说controller部分。</p>
<p>&#160;</p>
<h3>控制器</h3>
<p>在本例中，有3个控制器代码：</p>
<ul>
<li>edit，显示空的图书表单页，如果带id参数则显示填充图书记录的页面</li>
<li>getNotRelatedBooks，用于对话框异步获取可选择的相关图书</li>
<li>save，保存，如果是新记录，创建，已存在的，更新</li>
</ul>
<p>完整的源代码：</p>
<blockquote><p>class BookController {</p>
<p>&#160;&#160;&#160; def book</p>
<p>&#160;&#160;&#160; //编辑，用于新建和修改     <br />&#160;&#160;&#160; def edit={      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; book=Book.get(params.id)      <br />&#160;&#160;&#160; }      <br />&#160;&#160;&#160; <br />&#160;&#160;&#160; //保存，新建和修改的保存      <br />&#160;&#160;&#160; def save={      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; book=new Book(params)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; book.save()      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; redirect(action:&#8217;edit&#8217;,id:book.id)      <br />&#160;&#160;&#160; }</p>
<p>&#160;&#160;&#160; //对话框获取符合条件的相关图书     <br />&#160;&#160;&#160; def getNotRelatedBooks={      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; def bookId=params.id      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; def notRelatedBooks=[]</p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160; def hql=&#8217;from Book b&#8217;     <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; if(params.id){      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; def relatedIds=request.getParameterValues(&#8216;related_id&#8217;)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; hql+=&#8217; where b.id not in (&#8216;</p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; if(relatedIds &amp;&amp; relatedIds.size()&gt;0){     <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; relatedIds.each { relatedId-&gt; hql&lt;&lt;=&quot;&quot;&quot;&#8217;${relatedId}&#8217;,&quot;&quot;&quot; }      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }</p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; hql&lt;&lt;=&quot;&quot;&quot;&#8217;${bookId}&#8217;)&quot;&quot;&quot;     <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; }      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; notRelatedBooks=Book.findAll(hql)</p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160; render(contentType:&quot;text/json&quot;){     <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; array{      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; for(b in notRelatedBooks){      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; book id:b.id, name:b.name      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; }      <br />&#160;&#160;&#160; }      <br />}</p>
</blockquote>
<p>也可以从这里下载完整的项目：</p>
<blockquote><p><a title="http://easymorse.googlecode.com/svn/tags/BookProto-0.1" href="http://easymorse.googlecode.com/svn/tags/BookProto-0.1">http://easymorse.googlecode.com/svn/tags/BookProto-0.1</a></p>
</blockquote>
]]></content:encoded>
			<wfw:commentRss>http://marshal.easymorse.com/archives/4470/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Grails实现备选相关图书</title>
		<link>http://marshal.easymorse.com/archives/4460?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=grails%25e5%25ae%259e%25e7%258e%25b0%25e5%25a4%2587%25e9%2580%2589%25e7%259b%25b8%25e5%2585%25b3%25e5%259b%25be%25e4%25b9%25a6</link>
		<comments>http://marshal.easymorse.com/archives/4460#comments</comments>
		<pubDate>Sun, 10 Jul 2011 09:17:01 +0000</pubDate>
		<dc:creator>Marshal</dc:creator>
				<category><![CDATA[计算机技术]]></category>
		<category><![CDATA[grails]]></category>
		<category><![CDATA[grails gorm]]></category>
		<category><![CDATA[grails json]]></category>
		<category><![CDATA[groovy]]></category>
		<category><![CDATA[web app]]></category>

		<guid isPermaLink="false">http://marshal.easymorse.com/archives/4460</guid>
		<description><![CDATA[比如，由管理员手工挑选图书的相关图书。从用户体验上，这个选择，应该是异步的。即使用ajax。 当用户在图书编辑页面时，点击相关图书的增加按钮，应该弹出对话框，对话框中包含了可选的备选图书。 这里要保证，如果是编辑（不是新的图书），那么不能包含自身，也不能包含已经选择了的相关图书。 这里还是在Grails创建和更新实体使用相同的视图基础上做的修改。 首先，要矫正Grails实现实体自身多对多的变通方案中的一处错误。对SortedSet的错误使用。它并不是为了对集合结果做按照添加次序存储的，而是通过Compare接口比较来做排序的。因此把BookRelation改为： package bookproto class BookRelation { &#160;&#160;&#160; static constraints = { &#160;&#160;&#160; } &#160;&#160;&#160; &#160;&#160;&#160; static hasMany=[books:Book] &#160;&#160;&#160; static belongsTo=[book:Book] } 而后，为了制造测试数据，即，测试图书必须包含多个图书，另外，要有一些图书是不属于测试图书的相关图书的。这里需要类似Grails开发环境下如何生成模拟测试数据，做一些改动： import bookproto.Book import bookproto.BookRelation; class BootStrap { &#160;&#160;&#160; def init = { servletContext -&#62; &#160;&#160;&#160;&#160;&#160;&#160;&#160; environments{ &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; development{ &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; def testBook=new Book(name:&#34;中国史学史&#34;) &#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; def relatedBooks=[] &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; def book=new [...]]]></description>
			<content:encoded><![CDATA[<p>比如，由管理员手工挑选图书的相关图书。从用户体验上，这个选择，应该是异步的。即使用ajax。</p>
<p>当用户在图书编辑页面时，点击相关图书的增加按钮，应该弹出对话框，对话框中包含了可选的备选图书。</p>
<p><a href="http://marshal.easymorse.com/wp-content/uploads/2011/07/image9.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://marshal.easymorse.com/wp-content/uploads/2011/07/image_thumb9.png" width="244" height="168" /></a></p>
<p>这里要保证，如果是编辑（不是新的图书），那么不能包含自身，也不能包含已经选择了的相关图书。</p>
<p><span id="more-4460"></span>
<p>这里还是在<a href="http://marshal.easymorse.com/archives/4457" title="Grails创建和更新实体使用相同的视图">Grails创建和更新实体使用相同的视图</a>基础上做的修改。</p>
<p>首先，要矫正<a href="http://marshal.easymorse.com/archives/4438" title="Grails实现实体自身多对多的变通方案">Grails实现实体自身多对多的变通方案</a>中的一处错误。对SortedSet的错误使用。它并不是为了对集合结果做按照添加次序存储的，而是通过Compare接口比较来做排序的。因此把BookRelation改为：</p>
<blockquote><p>package bookproto</p>
<p>class BookRelation {</p>
<p>&#160;&#160;&#160; static constraints = {     <br />&#160;&#160;&#160; }      <br />&#160;&#160;&#160; <br />&#160;&#160;&#160; static hasMany=[books:Book]      <br />&#160;&#160;&#160; static belongsTo=[book:Book]      <br />}</p>
</blockquote>
<p>而后，为了制造测试数据，即，测试图书必须包含多个图书，另外，要有一些图书是不属于测试图书的相关图书的。这里需要类似<a href="http://marshal.easymorse.com/archives/4447" title="Grails开发环境下如何生成模拟测试数据">Grails开发环境下如何生成模拟测试数据</a>，做一些改动：</p>
<blockquote><p>import bookproto.Book     <br />import bookproto.BookRelation;</p>
<p>class BootStrap {</p>
<p>&#160;&#160;&#160; def init = { servletContext -&gt;     <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; environments{      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; development{      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; def testBook=new Book(name:&quot;中国史学史&quot;)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; def relatedBooks=[]      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; def book=new Book(name:&quot;中国通史&quot;)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; book.save()      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; println &quot;&quot;&quot;related book id: ${book.id}&quot;&quot;&quot;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; relatedBooks.add(book)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; book=new Book(name:&quot;中国农业史&quot;)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; book.save()      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; println &quot;&quot;&quot;related book id: ${book.id}&quot;&quot;&quot;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; relatedBooks.add(book)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; testBook.bookRelation=new BookRelation(book:testBook,relatedBooks:relatedBooks)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; testBook.save()      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; println &quot;&quot;&quot;test book id: ${testBook.id}&quot;&quot;&quot;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; book=new Book(name:&quot;中国战争史&quot;)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; book.save()      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; book=new Book(name:&quot;中国商业发展史&quot;)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; book.save()      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; book=new Book(name:&quot;中国科学技术史&quot;)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; book.save()      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; }      <br />&#160;&#160;&#160; }      <br />&#160;&#160;&#160; def destroy = {      <br />&#160;&#160;&#160; }      <br />}</p>
</blockquote>
<p>下面是编写controller对应的方法：</p>
<blockquote><p>def getNotRelatedBooks={     <br />&#160;&#160;&#160; def bookId=params.id      <br />&#160;&#160;&#160; def notRelatedBooks=[]</p>
<p>&#160;&#160;&#160; if(params.id){     <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; def relatedIds=request.getParameterValues(&#8216;related_id&#8217;)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; def hql=&#8217;from Book b where b.id not in (&#8216;</p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160; if(relatedIds &amp;&amp; relatedIds.size()&gt;0){     <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; relatedIds.each { relatedId-&gt; hql&lt;&lt;=&quot;&quot;&quot;&#8217;${relatedId}&#8217;,&quot;&quot;&quot; }      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; }</p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160; hql&lt;&lt;=&quot;&quot;&quot;&#8217;${bookId}&#8217;)&quot;&quot;&quot;     </p>
<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160; notRelatedBooks=Book.findAll(hql)     <br />&#160;&#160;&#160; }      <br />&#160;&#160;&#160;&#160; <br />&#160;&#160;&#160; render(contentType:&quot;text/json&quot;){      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; array{      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; for(b in notRelatedBooks){      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; book id:b.id, name:b.name      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; }      <br />&#160;&#160;&#160; }      <br />}</p>
</blockquote>
<p>这里需要注意的是以下几个地方：</p>
<ul>
<li>针对同名的多个参数传递，测试了一下，grails识别的不好，因此直接使用servlet的api，即：request.getParameterValues获取了字符串数组</li>
<li>hql的使用，这里的not in写法，效率不高，因为客户的数据量不大，先这样写</li>
<li>使用render方法，返回json数据</li>
</ul>
<p>测试是否成功，比如发送get请求，url：</p>
<blockquote><p><a title="http://localhost:8080/BookProto/book/getNotRelatedBooks?id=4028802c-311343b6-0131-1343c6d7-0003&amp;related_id=4028802c-311343b6-0131-1343c69b-0001&amp;related_id=4028802c-311343b6-0131-1343c6a7-0002" href="http://localhost:8080/BookProto/book/getNotRelatedBooks?id=4028802c-311343b6-0131-1343c6d7-0003&amp;related_id=4028802c-311343b6-0131-1343c69b-0001&amp;related_id=4028802c-311343b6-0131-1343c6a7-0002">http://localhost:8080/BookProto/book/getNotRelatedBooks?id=4028802c-311343b6-0131-1343c6d7-0003&amp;related_id=4028802c-311343b6-0131-1343c69b-0001&amp;related_id=4028802c-311343b6-0131-1343c6a7-0002</a></p>
<p>   <font style="background-color: #ffffff"></font></p></blockquote>
<p>然后，获得的数据类似这样：</p>
<blockquote><p>[{&quot;id&quot;:&quot;4028802c-311343b6-0131-1343c6fa-0004&quot;,&quot;name&quot;:&quot;中国战争史&quot;},{&quot;id&quot;:&quot;4028802c-311343b6-0131-1343c6fe-0005&quot;,&quot;name&quot;:&quot;中国商业发展史&quot;},{&quot;id&quot;:&quot;4028802c-311343b6-0131-1343c701-0006&quot;,&quot;name&quot;:&quot;中国科学技术史&quot;}]</p>
</blockquote>
<p>检查了一下，没有问题。</p>
]]></content:encoded>
			<wfw:commentRss>http://marshal.easymorse.com/archives/4460/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Grails创建和更新实体使用相同的视图</title>
		<link>http://marshal.easymorse.com/archives/4457?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=grails%25e5%2588%259b%25e5%25bb%25ba%25e5%2592%258c%25e6%259b%25b4%25e6%2596%25b0%25e5%25ae%259e%25e4%25bd%2593%25e4%25bd%25bf%25e7%2594%25a8%25e7%259b%25b8%25e5%2590%258c%25e7%259a%2584%25e8%25a7%2586%25e5%259b%25be</link>
		<comments>http://marshal.easymorse.com/archives/4457#comments</comments>
		<pubDate>Sun, 10 Jul 2011 07:06:40 +0000</pubDate>
		<dc:creator>Marshal</dc:creator>
				<category><![CDATA[计算机技术]]></category>
		<category><![CDATA[grails]]></category>
		<category><![CDATA[groovy]]></category>
		<category><![CDATA[web app]]></category>

		<guid isPermaLink="false">http://marshal.easymorse.com/archives/4457</guid>
		<description><![CDATA[创建和更新实体，是使用相同的视图还是不同的视图。这需要权衡。如果我来写，一般我希望是相同的，即，无论是新建图书还是编辑图书，使用相同的gsp页面。好处是便于维护。如果新建和编辑的流程出入较大，也是出于维护的角度，可能会考虑使用不同的视图。 以下写个简单的使用相同视图实现创建和更新的示例。示例是在Grails开发环境下如何生成模拟测试数据基础上做了改动。 实现的效果类似这样，如果是新增，链接中没有参数，显示空白的表单页面： 否则： 先说一下服务器端代码： class BookController { &#160;&#160;&#160; def book &#160;&#160;&#160; &#160;&#160;&#160; def edit={ &#160;&#160;&#160;&#160;&#160;&#160;&#160; book=Book.get(params.id) &#160;&#160;&#160; } &#160; 很简单，就是根据id从数据库中查找得到book对象。当然get不到book对象，这里和hibernate的get方法类似，是返回null的。 客户端的html页面部分，通过gsp脚本，如果去到book对象，就把它赋值给javascript中的book对象。之后，判断如果js的book对象有值，则用改值初始化表单： &#60;html&#62; &#60;head&#62; &#60;title&#62;编辑图书&#60;/title&#62; &#60;meta name=&#34;layout&#34; content=&#34;main&#34;&#62;&#60;/meta&#62; &#60;script type=&#34;text/javascript&#34;&#62; &#60;% //初始化javascript的book对象 if(book){ &#160;&#160;&#160; out&#60;&#60;&#34;&#34;&#34;var book={id:&#8217;${book.id}&#8217;,name:&#8217;${book.name}&#8217;};&#34;&#34;&#34; }else{ &#160;&#160;&#160; out&#60;&#60;&#34;&#34;&#34;var book={};&#34;&#34;&#34; } %&#62; function initBookForm(){ &#160;&#160;&#160; $(&#34;#bookName&#34;).val(book.name); } &#60;/script&#62; &#60;/head&#62; &#60;body&#62; &#60;div class=&#34;content&#34; style=&#34;margin-left: 20px;&#34;&#62; &#60;form [...]]]></description>
			<content:encoded><![CDATA[<p>创建和更新实体，是使用相同的视图还是不同的视图。这需要权衡。如果我来写，一般我希望是相同的，即，无论是新建图书还是编辑图书，使用相同的gsp页面。好处是便于维护。如果新建和编辑的流程出入较大，也是出于维护的角度，可能会考虑使用不同的视图。</p>
<p>以下写个简单的使用相同视图实现创建和更新的示例。示例是在<a href="http://marshal.easymorse.com/archives/4447" title="Grails开发环境下如何生成模拟测试数据">Grails开发环境下如何生成模拟测试数据</a>基础上做了改动。</p>
<p>实现的效果类似这样，如果是新增，链接中没有参数，显示空白的表单页面：</p>
<p><a href="http://marshal.easymorse.com/wp-content/uploads/2011/07/image7.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://marshal.easymorse.com/wp-content/uploads/2011/07/image_thumb7.png" width="244" height="157" /></a></p>
<p>否则：</p>
<p><a href="http://marshal.easymorse.com/wp-content/uploads/2011/07/image8.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://marshal.easymorse.com/wp-content/uploads/2011/07/image_thumb8.png" width="244" height="129" /></a></p>
<p><span id="more-4457"></span>
<p>先说一下服务器端代码：</p>
<blockquote><p>class BookController {</p>
<p>&#160;&#160;&#160; def book     <br />&#160;&#160;&#160; <br />&#160;&#160;&#160; def edit={      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; book=Book.get(params.id)      <br />&#160;&#160;&#160; }</p>
<p>&#160;</p>
</blockquote>
<p>很简单，就是根据id从数据库中查找得到book对象。当然get不到book对象，这里和hibernate的get方法类似，是返回null的。</p>
<p>客户端的html页面部分，通过gsp脚本，如果去到book对象，就把它赋值给javascript中的book对象。之后，判断如果js的book对象有值，则用改值初始化表单：</p>
<blockquote><p>&lt;html&gt;     <br />&lt;head&gt;      <br />&lt;title&gt;编辑图书&lt;/title&gt;      <br />&lt;meta name=&quot;layout&quot; content=&quot;main&quot;&gt;&lt;/meta&gt;      <br />&lt;script type=&quot;text/javascript&quot;&gt;      <br />&lt;%      <br />//初始化javascript的book对象      <br />if(book){      <br />&#160;&#160;&#160; out&lt;&lt;&quot;&quot;&quot;var book={id:&#8217;${book.id}&#8217;,name:&#8217;${book.name}&#8217;};&quot;&quot;&quot;      <br />}else{      <br />&#160;&#160;&#160; out&lt;&lt;&quot;&quot;&quot;var book={};&quot;&quot;&quot;      <br />}      <br />%&gt;</p>
<p>function initBookForm(){     <br />&#160;&#160;&#160; $(&quot;#bookName&quot;).val(book.name);      <br />}      <br />&lt;/script&gt;      <br />&lt;/head&gt;      <br />&lt;body&gt;      <br />&lt;div class=&quot;content&quot; style=&quot;margin-left: 20px;&quot;&gt;      <br />&lt;form action=&quot;save&quot;&gt;      <br />&lt;div&gt;书名：&lt;input id=&quot;bookName&quot; type=&quot;text&quot;/&gt;&lt;/div&gt;      <br />&lt;p /&gt;      <br />&lt;div&gt;相关图书：&lt;input id=&quot;relativeBooks&quot; type=&quot;button&quot; value=&quot;增加&quot;/&gt;&lt;/div&gt;      <br />&lt;div id=&quot;relativeBooks&quot;&gt;&lt;/div&gt;      <br />&lt;p /&gt;      <br />&lt;hr width=&quot;250px&quot; style=&quot;margin-top: 10px;margin-bottom: 10px;&quot;/&gt;      <br />&lt;input type=&quot;submit&quot; value=&quot;保存&quot;/&gt;      <br />&lt;/form&gt;      <br />&lt;/div&gt;      <br />&lt;script type=&quot;text/javascript&quot;&gt;      <br />if (book.id!=null){      <br />&#160;&#160;&#160; initBookForm();      <br />}      <br />&lt;/script&gt;      <br />&lt;/body&gt;      <br />&lt;/html&gt;</p>
</blockquote>
]]></content:encoded>
			<wfw:commentRss>http://marshal.easymorse.com/archives/4457/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>使用Grails布局功能</title>
		<link>http://marshal.easymorse.com/archives/4452?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=%25e4%25bd%25bf%25e7%2594%25a8grails%25e5%25b8%2583%25e5%25b1%2580%25e5%258a%259f%25e8%2583%25bd</link>
		<comments>http://marshal.easymorse.com/archives/4452#comments</comments>
		<pubDate>Sun, 10 Jul 2011 06:52:08 +0000</pubDate>
		<dc:creator>Marshal</dc:creator>
				<category><![CDATA[计算机技术]]></category>
		<category><![CDATA[grails]]></category>
		<category><![CDATA[groovy]]></category>
		<category><![CDATA[sitemesh]]></category>

		<guid isPermaLink="false">http://marshal.easymorse.com/archives/4452</guid>
		<description><![CDATA[Grails使用的是SiteMesh。这是最早在WebWork下使用的布局引擎。但是随着Struts的普及，Tiles的使用远远超过了SiteMesh。不过SiteMesh实现的布局很简介优雅，不需要编写很多劳什子的布局xml文件。 现在SiteMesh集成进Grails，使用起来也更简单了。下面的截图，就是使用SiteMesh的页面。 Grails已经生成了默认的布局文件，在这里： 我在这个文件中加上了JQuery的支持： &#60;!DOCTYPE html&#62; &#60;html&#62; &#160;&#160;&#160; &#60;head&#62; &#160;&#160;&#160;&#160;&#160;&#160;&#160; &#60;title&#62;&#60;g:layoutTitle default=&#34;Grails&#34; /&#62;&#60;/title&#62; &#160;&#160;&#160;&#160;&#160;&#160;&#160; &#60;link rel=&#34;stylesheet&#34; href=&#34;${resource(dir:&#8217;css&#8217;,file:&#8217;main.css&#8217;)}&#34; /&#62; &#160;&#160;&#160;&#160;&#160;&#160;&#160; &#60;link rel=&#34;shortcut icon&#34; href=&#34;${resource(dir:&#8217;images&#8217;,file:&#8217;favicon.ico&#8217;)}&#34; type=&#34;image/x-icon&#34; /&#62; &#160;&#160;&#160;&#160;&#160;&#160;&#160; &#60;g:javascript library=&#34;jquery&#34; plugin=&#34;jquery&#34; /&#62; &#160;&#160;&#160;&#160;&#160;&#160;&#160; &#60;jqui:resources /&#62; &#160;&#160;&#160;&#160;&#160;&#160;&#160; &#60;g:layoutHead /&#62; &#160;&#160;&#160;&#160;&#160;&#160; &#160;&#160;&#160; &#60;/head&#62; &#160;&#160;&#160; &#60;body&#62; &#160;&#160;&#160;&#160;&#160;&#160;&#160; &#60;div id=&#34;spinner&#34; class=&#34;spinner&#34; style=&#34;display:none;&#34;&#62; &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#60;img src=&#34;${resource(dir:&#8217;images&#8217;,file:&#8217;spinner.gif&#8217;)}&#34; alt=&#34;${message(code:&#8217;spinner.alt&#8217;,default:&#8217;Loading&#8230;&#8217;)}&#34; /&#62; &#160;&#160;&#160;&#160;&#160;&#160;&#160; &#60;/div&#62; &#160;&#160;&#160;&#160;&#160;&#160;&#160; &#60;div [...]]]></description>
			<content:encoded><![CDATA[<p>Grails使用的是SiteMesh。这是最早在WebWork下使用的布局引擎。但是随着Struts的普及，Tiles的使用远远超过了SiteMesh。不过SiteMesh实现的布局很简介优雅，不需要编写很多劳什子的布局xml文件。</p>
<p>现在SiteMesh集成进Grails，使用起来也更简单了。下面的截图，就是使用SiteMesh的页面。</p>
<p><a href="http://marshal.easymorse.com/wp-content/uploads/2011/07/image5.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://marshal.easymorse.com/wp-content/uploads/2011/07/image_thumb5.png" width="244" height="226" /></a></p>
<p><span id="more-4452"></span>
<p>Grails已经生成了默认的布局文件，在这里：</p>
<p><a href="http://marshal.easymorse.com/wp-content/uploads/2011/07/image6.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://marshal.easymorse.com/wp-content/uploads/2011/07/image_thumb6.png" width="165" height="244" /></a></p>
<p>我在这个文件中加上了JQuery的支持：</p>
<blockquote><p>&lt;!DOCTYPE html&gt;     <br />&lt;html&gt;      <br />&#160;&#160;&#160; &lt;head&gt;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;title&gt;&lt;g:layoutTitle default=&quot;Grails&quot; /&gt;&lt;/title&gt;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;link rel=&quot;stylesheet&quot; href=&quot;${resource(dir:&#8217;css&#8217;,file:&#8217;main.css&#8217;)}&quot; /&gt;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;link rel=&quot;shortcut icon&quot; href=&quot;${resource(dir:&#8217;images&#8217;,file:&#8217;favicon.ico&#8217;)}&quot; type=&quot;image/x-icon&quot; /&gt;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; <em><strong>&lt;g:javascript library=&quot;jquery&quot; plugin=&quot;jquery&quot; /&gt;         <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;jqui:resources /&gt;          <br /></strong></em>&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;g:layoutHead /&gt;      <br />&#160;&#160;&#160;&#160;&#160;&#160; <br />&#160;&#160;&#160; &lt;/head&gt;      <br />&#160;&#160;&#160; &lt;body&gt;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;div id=&quot;spinner&quot; class=&quot;spinner&quot; style=&quot;display:none;&quot;&gt;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;img src=&quot;${resource(dir:&#8217;images&#8217;,file:&#8217;spinner.gif&#8217;)}&quot; alt=&quot;${message(code:&#8217;spinner.alt&#8217;,default:&#8217;Loading&#8230;&#8217;)}&quot; /&gt;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;/div&gt;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;div id=&quot;grailsLogo&quot;&gt;&lt;a href=&quot;<a href="http://grails.org&quot;">http://grails.org&quot;</a>&gt;&lt;img src=&quot;${resource(dir:&#8217;images&#8217;,file:&#8217;grails_logo.png&#8217;)}&quot; alt=&quot;Grails&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;&lt;/div&gt;      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;g:layoutBody /&gt;      <br />&#160;&#160;&#160; &lt;/body&gt;      <br />&lt;/html&gt;</p>
</blockquote>
<p>对这部分有不明白的，参加这里<a href="http://marshal.easymorse.com/archives/4435" title="Grails从1.4m1版本回退到1.3.7">Grails从1.4m1版本回退到1.3.7</a>。</p>
<p>如果希望自己的gsp页面，使用这个布局，也很简单：</p>
<blockquote><p>&lt;html&gt;     <br />&lt;head&gt;      <br />&lt;title&gt;编辑图书&lt;/title&gt;      <br /><em><strong>&lt;meta name=&quot;layout&quot; content=&quot;main&quot;&gt;&lt;/meta&gt;</strong></em></p>
<p>&#8230;&#8230;</p>
</blockquote>
<p><font style="background-color: #ffffff">只需加上斜体字部分即可，content的值，对应布局文件的名称。</font></p>
]]></content:encoded>
			<wfw:commentRss>http://marshal.easymorse.com/archives/4452/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Grails开发环境下如何生成模拟测试数据</title>
		<link>http://marshal.easymorse.com/archives/4447?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=grails%25e5%25bc%2580%25e5%258f%2591%25e7%258e%25af%25e5%25a2%2583%25e4%25b8%258b%25e5%25a6%2582%25e4%25bd%2595%25e7%2594%259f%25e6%2588%2590%25e6%25a8%25a1%25e6%258b%259f%25e6%25b5%258b%25e8%25af%2595%25e6%2595%25b0%25e6%258d%25ae</link>
		<comments>http://marshal.easymorse.com/archives/4447#comments</comments>
		<pubDate>Sun, 10 Jul 2011 06:36:52 +0000</pubDate>
		<dc:creator>Marshal</dc:creator>
				<category><![CDATA[计算机技术]]></category>
		<category><![CDATA[grails]]></category>
		<category><![CDATA[groovy]]></category>
		<category><![CDATA[web app]]></category>

		<guid isPermaLink="false">http://marshal.easymorse.com/archives/4447</guid>
		<description><![CDATA[开发环境下经常需要一些模拟测试数据。比如数据库中要有一些初始的实体。Grails提供了很好的办法。可在BootStrap.groovy中： 编写只针对开发环境的初始化代码： import bookproto.Book class BootStrap { &#160;&#160;&#160; def init = { servletContext -&#62; &#160;&#160;&#160;&#160;&#160;&#160;&#160; environments{ &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; development{ &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; def book=new Book(name:&#34;中国史学史&#34;) &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; book.save() &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; println &#34;&#34;&#34;book id: ${book.id}&#34;&#34;&#34; &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; } &#160;&#160;&#160;&#160;&#160;&#160;&#160; } &#160;&#160;&#160; } &#160;&#160;&#160; def destroy = { &#160;&#160;&#160; } } 以上示例是在Grails的GORM中使用uuid基础上做的处理。]]></description>
			<content:encoded><![CDATA[<p>开发环境下经常需要一些模拟测试数据。比如数据库中要有一些初始的实体。Grails提供了很好的办法。可在BootStrap.groovy中：</p>
<p><a href="http://marshal.easymorse.com/wp-content/uploads/2011/07/image4.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://marshal.easymorse.com/wp-content/uploads/2011/07/image_thumb4.png" width="235" height="173" /></a></p>
<p>编写只针对开发环境的初始化代码：</p>
<blockquote><p>import bookproto.Book</p>
<p>class BootStrap {</p>
<p>&#160;&#160;&#160; def init = { servletContext -&gt;     <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; <em><strong>environments{         <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; development{          <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; def book=new Book(name:&quot;中国史学史&quot;)          <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; book.save()          <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; println &quot;&quot;&quot;book id: ${book.id}&quot;&quot;&quot;          <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }          <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; }          <br /></strong></em>&#160;&#160;&#160; }      <br />&#160;&#160;&#160; def destroy = {      <br />&#160;&#160;&#160; }      <br />}</p>
</blockquote>
<p>以上示例是在<a href="http://marshal.easymorse.com/archives/4440" title="Grails的GORM中使用uuid">Grails的GORM中使用uuid</a>基础上做的处理。</p>
]]></content:encoded>
			<wfw:commentRss>http://marshal.easymorse.com/archives/4447/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Grails的GORM中使用uuid</title>
		<link>http://marshal.easymorse.com/archives/4440?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=grails%25e7%259a%2584gorm%25e4%25b8%25ad%25e4%25bd%25bf%25e7%2594%25a8uuid-2</link>
		<comments>http://marshal.easymorse.com/archives/4440#comments</comments>
		<pubDate>Fri, 08 Jul 2011 07:22:49 +0000</pubDate>
		<dc:creator>Marshal</dc:creator>
				<category><![CDATA[计算机技术]]></category>
		<category><![CDATA[grails]]></category>
		<category><![CDATA[grails gorm]]></category>
		<category><![CDATA[groovy]]></category>

		<guid isPermaLink="false">http://marshal.easymorse.com/archives/4440</guid>
		<description><![CDATA[开发中，有很多情况需要使用不连续的随机生成的唯一标识。好处是： 可以隐藏商业细节，比如生成订单，自增列会暴露商业秘密 另外，自增列往往依赖数据库生成，对集群和分布式部署应用有影响，还有就是导入数据时需要对数据库自增列做设置 因此，使用uuid做实体唯一标识的地方很常见。在Grails实现实体自身多对多的变通方案基础上，把默认的native生成主键的方式改为使用uuid： class Book { &#160;&#160;&#160; static constraints = { &#160;&#160;&#160;&#160;&#160;&#160;&#160; bookRelation(nullable:true) &#160;&#160;&#160; } &#160;&#160;&#160; &#160;&#160;&#160; static mapping = { &#160;&#160;&#160;&#160;&#160;&#160;&#160; id generator:&#8217;uuid.hex&#8217;, params:[separator:'-'] &#160;&#160; } &#160;&#160;&#160; &#160;&#160; String id &#160;&#160;&#160; String name &#160;&#160;&#160; BookRelation bookRelation }]]></description>
			<content:encoded><![CDATA[<p>开发中，有很多情况需要使用不连续的随机生成的唯一标识。好处是：</p>
<ul>
<li>可以隐藏商业细节，比如生成订单，自增列会暴露商业秘密</li>
<li>另外，自增列往往依赖数据库生成，对集群和分布式部署应用有影响，还有就是导入数据时需要对数据库自增列做设置</li>
</ul>
<p>因此，使用uuid做实体唯一标识的地方很常见。在<a href="http://marshal.easymorse.com/archives/4438" title="Grails实现实体自身多对多的变通方案">Grails实现实体自身多对多的变通方案</a>基础上，把默认的native生成主键的方式改为使用uuid：</p>
<blockquote><p>class Book {</p>
<p>&#160;&#160;&#160; static constraints = {     <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; bookRelation(nullable:true)      <br />&#160;&#160;&#160; }      <br />&#160;&#160;&#160; <br />&#160;&#160;&#160; <em><strong>static mapping = {         <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; id generator:&#8217;uuid.hex&#8217;, params:[separator:'-']          <br />&#160;&#160; }          <br /></strong></em>&#160;&#160;&#160; <br />&#160;&#160; <em><strong> String id         <br /></strong></em>&#160;&#160;&#160; String name      <br />&#160;&#160;&#160; BookRelation bookRelation      <br />}</p>
</blockquote>
]]></content:encoded>
			<wfw:commentRss>http://marshal.easymorse.com/archives/4440/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

