Ŀ ¼

1. ¼ò½é
2. Æð²½
2.1 ÏÂÔØ²¢°²×°Grails
2.2 ´´½¨Ò»¸öGrailsÓ¦ÓÃ
2.3 Hello WorldʾÀý
2.4 ʹÓÃIDE
2.5 ¹æÔ¼ÅäÖÃ
2.6 ÔËÐÐGrailsÓ¦ÓÃ
2.7 ²âÊÔGrailsÓ¦ÓÃ
2.8 ²¿ÊðGrailsÓ¦ÓÃ
2.9 ËùÖ§³ÖµÄJava EEÈÝÆ÷
2.10 ´´½¨¹¤¼þ
2.11 Éú³ÉGrailsÓ¦ÓÃ
3. ÅäÖÃ
3.1 »ù±¾ÅäÖÃ
3.1.1 ÄÚÖÃÑ¡Ïî
3.1.2 ÈÕÖ¾
3.2 »·¾³
3.3 Êý¾ÝÔ´
3.3.1 Êý¾ÝÔ´ºÍ»·¾³
3.3.2 JNDIÊý¾ÝÔ´
3.3.3 ×Ô¶¯Êý¾Ý¿âÒÆÖ²
3.4 ÍⲿÅäÖÃ
3.5 ¶¨Òå°æ±¾
4. ÃüÁîÐÐ
4.1 ´´½¨Gant½Å±¾
4.2 ¿É¸´ÓõÄGrails½Å±¾
4.3 ½Å±¾ÖеÄʼþ
4.4 AntºÍMaven
5. ¶ÔÏó¹ØÏµÓ³Éä(GORM)
5.1 ¿ìËÙÖ¸ÄÏ
5.1.1 »ù±¾µÄCRUD
5.2 ÔÚGORMÖнøÐÐÁìÓò½¨Ä£
5.2.1 GORMÖеĹØÁª
5.2.1.1 Ò»¶ÔÒ»
5.2.1.2 Ò»¶Ô¶à
5.2.1.3 ¶à¶Ô¶à
5.2.2 GORMµÄ×éºÏ
5.2.3 GORMµÄ¼Ì³Ð
5.2.4 ¼¯ºÏ¡¢ÁбíºÍÓ³Éä
5.3 ³Ö¾Ã»¯»ù´¡
5.3.1 ±£´æºÍ¸üÐÂ
5.3.2 ɾ³ý¶ÔÏó
5.3.3 ¼¶Áª¸üкÍɾ³ý
5.3.4 Á¢¼´¼ÓÔØºÍÑÓ³Ù¼ÓÔØ
5.3.4 ±¯¹ÛËøºÍÀÖ¹ÛËø
5.4 GORM²éѯ
5.4.1 ¶¯Ì¬²éÕÒÆ÷
5.4.2 Ìõ¼þ²éѯ
5.4.3 Hibernate²éѯÓïÑÔ
5.5 ¸ß¼¶GORMÌØÐÔ
5.5.1 ʼþºÍ×Ô¶¯ÊµÏÖʱ¼ä´Á
5.5.2 ×Ô¶¨ÒåORMÓ³Éä
5.5.2.1 ±íÃûºÍÁÐÃû
5.5.2.2 »º´æ²ßÂÔ
5.5.2.3 ¼Ì³Ð²ßÂÔ
5.5.2.4 ×Ô¶¨ÒåÊý¾Ý¿â±êʶ·û
5.5.2.5 ¸´ºÏÖ÷¼ü
5.5.2.6 Êý¾Ý¿âË÷Òý
5.5.2.7 ÀÖ¹ÛËøºÍ°æ±¾¶¨Òå
5.5.2.8 Á¢¼´¼ÓÔØºÍÑÓ³Ù¼ÓÔØ
5.6 ÊÂÎñ±à³Ì
5.7 GORMºÍÔ¼Êø
6. Web²ã
6.1 ¿ØÖÆÆ÷
6.1.1 Àí½â¿ØÖÆÆ÷ºÍ²Ù×÷
6.1.2 ¿ØÖÆÆ÷ºÍ×÷ÓÃÓò
6.1.3 Ä£ÐͺÍÊÓͼ
6.1.4 ÖØ¶¨ÏòºÍÁ´
6.1.5 ¿ØÖÆÆ÷À¹½ØÆ÷
6.1.6 Êý¾Ý°ó¶¨
6.1.7 XMLºÍJSONÏìÓ¦
6.1.8 ÉÏ´«Îļþ
6.1.9 ÃüÁî¶ÔÏó
6.2 Groovy Server Pages
6.2.1 GSP»ù´¡
6.2.1.1 ±äÁ¿ºÍ×÷ÓÃÓò
6.2.1.2 Âß¼­ºÍµü´ú
6.2.1.3 Ò³ÃæÖ¸Áî
6.2.1.4 ±í´ïʽ
6.2.2 GSP±êÇ©
6.2.2.1 ±äÁ¿ºÍ×÷ÓÃÓò
6.2.2.2 Âß¼­ºÍµü´ú
6.2.2.3 ËÑË÷ºÍ¹ýÂË
6.2.2.4 Á´½ÓºÍ×ÊÔ´
6.2.2.5 ±íµ¥ºÍ×Ö¶Î
6.2.2.6 ±êÇ©×÷Ϊ·½·¨µ÷ÓÃ
6.2.3 ÊÓͼºÍÄ£°å
6.2.4 ʹÓÃSitemesh²¼¾Ö
6.3 ±êÇ©¿â
6.3.1 ¼òµ¥±êÇ©
6.3.2 Âß¼­±êÇ©
6.3.3 µü´ú±êÇ©
6.3.4 ±êÇ©ÃüÃû¿Õ¼ä
6.4 URLÓ³Éä
6.4.1 Ó³Éäµ½¿ØÖÆÆ÷ºÍ²Ù×÷
6.4.2 ǶÈëʽ±äÁ¿
6.4.3 Ó³Éäµ½ÊÓͼ
6.4.4 Ó³Éäµ½ÏìÓ¦´úÂë
6.4.5 Ó³Éäµ½HTTP·½·¨
6.4.6 Ó³ÉäͨÅä·û
6.4.7 ×Ô¶¯ÖØÐ´Á´½Ó
6.4.8 Ó¦ÓÃÔ¼Êø
6.5 Web Flow
6.5.1 ¿ªÊ¼ºÍ½áÊø×´Ì¬
6.5.2 ²Ù×÷״̬ºÍÊÓͼ״̬
6.5.3 Á÷Ö´ÐÐʼþ
6.5.4 Á÷µÄ×÷ÓÃÓò
6.5.5 Êý¾Ý°ó¶¨ºÍÑéÖ¤
6.5.6 ×ÓÁ÷³ÌºÍ»á»°
6.6 ¹ýÂËÆ÷
6.6.1 Ó¦ÓùýÂËÆ÷
6.6.2 ¹ýÂËÆ÷µÄÀàÐÍ
6.6.3 ¹ýÂËÆ÷µÄ¹¦ÄÜ
6.7 Ajax
6.7.1 ÓÃPrototypeʵÏÖAjax
6.7.1.1 Òì²½Á´½Ó
6.7.1.2 ¸üÐÂÄÚÈÝ
6.7.1.3 Òì²½±íµ¥Ìá½»
6.7.1.4 Ajaxʼþ
6.7.2 ÓÃDojoʵÏÖAjax
6.7.3 ÓÃGWTʵÏÖAjax
6.7.4 ·þÎñ¶ËµÄAjax
6.8 ÄÚÈÝЭÉÌ
7. ÑéÖ¤
7.1 ÉùÃ÷Ô¼Êø
7.2 ÑéÖ¤Ô¼Êø
7.3 ¿Í»§¶ËÑéÖ¤
7.4 ÑéÖ¤ºÍ¹ú¼Ê»¯
8. ·þÎñ²ã
8.1 ÉùÃ÷ʽÊÂÎñ
8.2 ·þÎñµÄ×÷ÓÃÓò
8.3 ÒÀÀµ×¢ÈëºÍ·þÎñ
8.4 ʹÓÃJavaµÄ·þÎñ
9. ²âÊÔ
9.1 µ¥Ôª²âÊÔ
9.2 ¼¯³É²âÊÔ
9.3 ¹¦ÄܲâÊÔ
10. ¹ú¼Ê»¯
10.1 Àí½âÐÅÏ¢°ó¶¨
10.2 ¸Ä±äLocales
10.3 ¶ÁÈ¡ÐÅÏ¢
11. °²È«
11.1 Ô¤·À¹¥»÷
11.2 ×Ö·û´®µÄ±àÂëºÍ½âÂë
11.3 Éí·ÝÑéÖ¤
11.4 ¹ØÓÚ°²È«µÄ²å¼þ
11.4.1 Acegi
11.4.2 JSecurity
12 ²å¼þ
12.1 ´´½¨ºÍ°²×°²å¼þ
12.2 Àí½â²å¼þµÄ½á¹¹
12.3 Ìṩ»ù´¡µÄ¹¤¼þ
12.4 ÆÀ¹À¹æÔ¼
12.5 ²ÎÓë¹¹½¨Ê¼þ
12.6 ²ÎÓëÔËÐÐʱÅäÖÃ
12.7 ÔËÐÐʱÌí¼Ó¶¯Ì¬·½·¨
12.8 ²ÎÓë×Ô¶¯ÖØÔØ
12.9 Àí½â²å¼þ¼ÓÔØµÄ˳Ðò
13. Web·þÎñ
13.1 REST
13.2 SOAP
13.3 RSSºÍAtom
14. GrailsºÍSpring
14.1 GrailsµÄÖ§Öù
14.2 ÅäÖÃÆäËûBean
14.3 ͨ¹ýBeans DSLÔËÐÐSpring
14.4 ÅäÖÃÊôÐÔռλ
14.5 ÅäÖÃÊôÐÔÖØÔØ
15. GrailsºÍHibernate
15.1 ͨ¹ýHibernate×¢ÊͽøÐÐÓ³Éä
15.2 ÉîÈëÁ˽â
16.½ÅÊÖ¼Ü

1. ¼ò½é

Èç½ñµÄJava Web¿ª·¢¶ÔÓÚÐèÇóÀ´ËµÒѾ­±äµÃ¹ýÓÚ¸´ÔÓ¡£µ±½ñÖÚ¶àJavaÁìÓòµÄWeb¿ª·¢¿ò¼Ü²»½öʹÓø´ÔÓ£¬¶øÇÒ²¢Ã»ÓкܺõÄ×ñÑ­Don¡¯t Repeat Yourself£¨DRY£©Ô­Ôò¡£

ÏñRails£¬DjangoºÍTurboGearsÕâÑùµÄ¶¯Ì¬¿ò¼ÜÔÚWeb¿ª·¢ÁìÓò¿ª±ÙÁËÒ»ÌõеĵÀ·£¬Grails»ùÓÚÕâЩ¸ÅÄîÖ®ÉÏ£¬²ÉÓö¯Ì¬·½·¨¼õСÁËJavaƽ̨ÉϽøÐÐWeb¿ª·¢µÄ¸´ÔÓ¶È£¬²»¹ýÓëÄÇЩ¿ò¼Ü²»Í¬µÄÊÇ£¬GrailsÊǹ¹½¨ÔÚSpringºÍHibernateµÈJavaÒÑÓеļ¼ÊõÖ®Éϵġ£

GrailsÊÇÒ»¸öfull-stack¿ò¼Ü£¬Ëü½èÖúÓÚºËÐļ¼ÊõÓëÏà¹ØµÄ²å¼þ£¨plug-in£©À´½â¾öWeb¿ª·¢Öз½·½ÃæÃæµÄÎÊÌ⣬ÆäÖаüÀ¨£º

½èÖúÓÚ¹¦ÄÜÇ¿´óµÄGroovy¶¯Ì¬ÓïÑÔºÍÁìÓòÌØ¶¨ÓïÑÔ£¨Domain Specific Language£¬DSL£©£¬ÒÔÉÏÄÇÐ©ÌØÐÔ±äµÃ·Ç³£Ò×Óá£

ÕâÆªÎĵµ»áÏòÄã½éÉÜÈçºÎʹÓÃGrails¿ò¼ÜÀ´´î½¨WebÓ¦ÓóÌÐò¡£

2. Æð²½

2.1 ÏÂÔØ²¢°²×°Grails

Ê×ÏÈÐèÒªÏÂÔØGrailsµÄ·¢Ðаü²¢½øÐа²×°£¬Ö´Ðв½ÖèÈçÏ£º

Èç¹û»·¾³±äÁ¿ÉèÖÃÎÞÎ󣬴Ëʱ¿ÉÒÔ´ò¿ªÖÕ¶Ë£¨windowÏÂΪÃüÁîÌáʾ·û£¬Unix/LinuxÏÂΪShell£©£¬ÊäÈëgrails£¬Èç¹ûÆÁÄ»ÉÏÏÔʾÈçÏÂÌáʾÔò˵Ã÷°²×°³É¹¦¡£


Welcome to Grails 1.0 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: /Developer/grails-1.0
No script name specified. Use 'grails help' for more info

2.2 ´´½¨Ò»¸öGrailsÓ¦ÓÃ

ÔÚ´´½¨Ó¦ÓóÌÐò֮ǰ£¬ÏÈÊìϤһÏÂgrailsÃüÁîµÄʹÓã¨grailsÖеÄÃüÁî¶¼ÔÚÖÕ¶ËÖÐÊäÈ룬Çë²Î¿¼ÉÏÃæµÄ½²½â£©¡£

grails command name

ÏÖÔÚÎÒÃÇΪÁË´´½¨Ò»¸öGrailsÓ¦Óã¬ÐèÒªÊäÈëµÄÃüÁîÊÇcreate-app


grails create-app helloworld

ÕâÑù¾ÍÔÚµ±Ç°Ä¿Â¼Ï´´½¨ÁËÒ»¸öÃûΪhelloworld£¨¼´ÎÒÃǵÄÓ¦ÓóÌÐòÃû£©µÄÎļþ¼Ð£¬ÔÚÕâ¸öÎļþ¼ÐÖаüº¬ÁËÎÒÃÇÕâ¸öÏîÄ¿µÄÕû¸öÎļþĿ¼£¬¿ÉÒÔʹÓÃÈçÏÂÃüÁî½øÈëÕâ¸öĿ¼Öв鿴£º


cd helloworld

2.3 Hello WorldʾÀý

ΪÁËÍê³ÉÕâ¸ö¾­µäµÄHello WorldʾÀý£¬ÎÒÃÇÐèÒªÔËÐÐcreate-controllerÃüÁî

grails create-controller hello  

ÔËÐиÃÃüÁîºó»áÔÚgrails-app/controllerĿ¼Ï´´½¨Ò»¸öÃûΪHelloController.groovyµÄ¿ØÖÆÆ÷£¨¸ü¶à¹ØÓÚ¿ØÖÆÆ÷µÄÄÚÈÝÇë²Î¿¼¿ØÖÆÆ÷Ò»½Ú£©

¿ØÖÆÆ÷Ö÷ÒªÓÃÀ´Íê³É¶ÔWebÇëÇóµÄ´¦Àí£¬ÎÒÃÇÉÔ΢ÐÞ¸ÄһϿØÖÆÆ÷µÄÄÚÈÝ£¬Ê¹ËüÄܹ»ÔÚÒ³ÃæÉÏÊä³ö"Hello World!"µÄ×ÖÑù£¬´úÂëÈçÏÂ:


class HelloController {
	def world = {
		render "Hello World!"
	}
}                           

ÏÖÔÚ¿ØÖÆÆ÷ÒѾ­Íê³ÉÁË£¬½ÓÏÂÀ´ÒªÊ¹ÓÃrun-appÀ´Æô¶¯ÄÚÖõÄjetty·þÎñÆ÷ÔËÐиոմ´½¨µÄhelloworld³ÌÐò


grails run-app

ÔËÐкó»áÔÚ8080¶Ë¿Ú£¨Ä¬ÈÏ£¬¿ÉÒÔʹÓÃ-Dserver.portÀ´Ö¸¶¨¶Ë¿Ú£©Æô¶¯·þÎñÆ÷£¬È»ºóÔÚä¯ÀÀÆ÷ÖÐÊäÈëhttp://localhost:8080/helloworldÀ´Æô¶¯Ó¦ÓóÌÐò.

ÔËÐнá¹ûÈçÏÂͼËùʾ£º

Õâ¸öGrails¼ò½éÒ³ÃæÊÇÓÉweb-app/index.gspÀ´ÏÔʾµÄ¡£´ÓÉÏͼ¿ÉÒÔ¿´¼û¸Õ²Å´´½¨µÄ¿ØÖÆÆ÷£¬µã»÷Á´½ÓÖ®ºó»áÔÚä¯ÀÀÆ÷ÖÐÏÔʾHello World!µÄ×ÖÑù¡£

2.4 ʹÓÃIDE

IntelliJ IDEA

ÖÁ½ñΪֹ×î³ÉÊì¡¢×îÈ«ÃæµÄGroovy&Grails¿ª·¢¼¯³É¹¤¾ß¾ÍÊÇIntelliJ IDEA 7.0ºÍËüµÄJetGroovy²å¼þ¡£¶ÔÓÚ´óÐÍÏîÄ¿£¬GrailsÍŶÓÓÅÏÈÍÆ¼öʹÓÃIntelliJ¡£

TextMate

ÓÉÓÚGrailsÄ¿±ê¼¯ÖÐÓÚÈçºÎ¸ü¼ò½à£¬ËùÒÔÎÒÃÇ¿ÉÒÔʹÓÃһЩ¸ü¼òµ¥µÄ±à¼­Æ÷½øÐÐGroovy&GrailsµÄ¿ª·¢£¬ÀýÈçÔÚMacÉÏ¿ÉÒÔʹÓÃTextMate £¬ÏÂÔØµØÖ· Texmate bundles SVN

Eclipse

Eclipse µÄGroovy²å¼þ Ò²¿ÉÒÔÖ§³ÖÓï·¨¸ßÁÁ£¬´úÂë×Ô¶¯Íê³ÉµÈÌØÐÔ¡£

ÔÚGrailsµÄWikiÉÏÓиü¶à¹ØÓÚEclipse²å¼þµÄ̸ÂÛ¡£

GrailsÔÚ´´½¨Ó¦ÓÃʱ»á×Ô¶¯´´½¨EclipseµÄ¹¤³ÌÎļþ.projectºÍclasspath£¬ÕâÑùÈç¹ûÒªÔÚEclipseÖе¼ÈëÒ»¸öGrails¹¤³ÌÖ»ÐèÒªÔÚEclipseÖÐÓÒ»÷Package Explorer£¬Ñ¡ÔñImport£¬È»ºóÑ¡ÔñExisting project into Workspace£¬×îºóÖ¸¶¨GrailsµÄÏîĿĿ¼λÖá£

µã»÷OK£¬Finishºó»á×Ô¶¯½«Grails¹¤³Ìµ¼Èëµ½EclipseÖС£ ²¢×Ô¶¯´´½¨ºÏÊʵÄÔËÐÐÅäÖã¨Run Configuration£©£¬ÕâÑùÔÚEclipseÖÐÖ±½Óµã»÷Run¾Í¿ÉÒÔÔËÐÐGrailsµÄÏîÄ¿¡£

2.5 ¹æÔ¼ÅäÖÃ

GrailsÖеÄÅäÖÃ×ñÑ­¡°¹æÔ¼ÓÅÓÚÅäÖá±µÄÔ­Ôò£¬¼´Í¨¹ýÎļþµÄÃû³ÆºÍλÖÃÀ´Ìæ´úÏÔʽµÄÅäÖã¬Òò´ËÐèÒªÊìϤÒÔϼ¸¸öĿ¼½á¹¹µÄÓÃ;¡£

´Ë´¦½ö½öΪһ¸ö·ÖÀ࣬¾ßÌåµÄÇë²Î¿¼Ïà¹ØÕ½ڣº

2.6 ÔËÐÐGrailsÓ¦ÓÃ

¿ÉÒÔÔËÐÐrun-appÃüÁîÀ´Æô¶¯GrailsÄÚÖõÄJetty·þÎñÆ÷£¬Ä¬ÈÏÆô¶¯¶Ë¿ÚΪ8080
grails run-app

Èç¹û8080¶Ë¿ÚÒѾ­±»Õ¼Ó㬿ÉÒÔͨ¹ý server.port ²ÎÊýÖ¸¶¨ÆäËû¶Ë¿Ú

grails -Dserver.port=8090 run-app

¸ü¶à¹ØÓÚrun-appµÄÄÚÈÝ¿ÉÒÔ²ÎÕղο¼Ö¸ÄÏ¡£

2.7 ²âÊÔGrailsÓ¦ÓÃ

create-* ÃüÁî»áÔÚ test/integrationĿ¼ÏÂ×Ô¶¯´´½¨ÏàÓ¦µÄ²âÊÔÎļþ£¬¿ÉÒÔÔÚÕâЩ²âÊÔÎļþÖбàд²âÊÔÓÃÀýÀ´½øÐе¥Ôª²âÊԺͼ¯³É²âÊÔ£¬¹ØÓÚ²âÊÔµÄÄÚÈÝ¿ÉÒԲο¼²âÊÔÕ½ڡ£ÔËÐÐÕâЩ²âÊÔ¿ÉÒÔʹÓÃtest-appÃüÁî¡£
grails test-app

Grails»á×Ô¶¯´´½¨AntµÄbuild.xmlÎļþ£¬Ò²¿ÉÒÔͨ¹ýAntÀ´ÔËÐвâÊÔ(ÆäʵÔËÐеÄÊÇGrailsµÄtest-app)¡£

ant test

ÕâÑùµ±GrailsÓ¦ÓÃ×÷Ϊ³ÖÐø¼¯³Éƽ̨£¬ÀýÈçCruiseControlµÄÒ»²¿·Öʱ¾Í»áÊ®·Ö·½±ã

2.8 ²¿ÊðGrailsÓ¦ÓÃ

GrailsÓ¦ÓóÌÐòÒÔWebÓ¦Óù鵵£¨.WAR£©ÎļþµÄÐÎʽ½øÐв¿Ê𣬲¢ÇÒGrails»¹ÌṩÁËwarÃüÁîÀ´Ö´ÐÐÉú³É¹éµµÎļþ¡£
grails war

ÕâÌõÃüÁî»áÉú³Éµ±Ç°Ó¦ÓõÄwarÎļþ£¬¿ÉÒÔ°´ÕÕ·þÎñÆ÷ÈÝÆ÷µÄ²»Í¬½øÐÐÏàÓ¦µÄÅäÖÃ.

Ò»¶¨²»ÒªÊ¹ÓÃrun-appÃüÁîÀ´²¿ÊðGrails£¬ÒòΪ´ËÃüÁî»áÔÚÔËÐÐʱ×Ô¶¯¼ÓÔØ£¬ÕâÑù»á¶Ô·þÎñÆ÷µÄÐÔÄܺͿÉÀ©Õ¹ÐÔÓÐÑÏÖØÓ°Ïì¡£

µ±²¿ÊðʱӦµ±Í¨JVMµÄ-server²ÎÊýÀ´Îª·þÎñÆ÷·ÖÅä×ã¹»µÄÄÚ´æ¿Õ¼ä£¬ÍƼöµÄVM²ÎÊýÊÇ£º

-server -Xmx512M

2.9 ËùÖ§³ÖµÄJava EEÈÝÆ÷

Grails¿ÉÒÔÖ§³ÖµÄÏ൱¶àµÄWebÈÝÆ÷£¬°üÀ¨:

ËäÈ»ÔÚһЩ·þÎñÆ÷ÉÏÔËÐл¹´æÔÚBug£¬µ«ÊǴ󲿷ÖÇé¿ö϶¼¿ÉÒÔ¹¤×÷µÄºÜºÃ¡£ÔÚGrailsµÄWikiÉÏ¿ÉÒÔÕÒµ½¹ØÓÚÒÑÖª²¿ÊðÎÊÌâµÄÇåµ¥¡£

2.10 ´´½¨¹¤¼þ

³ýÁËÒÔÉϽéÉܵÄÃüÁGrails»¹ÓÐһϵÁÐÓÃÀ´´´½¨ÆäËû¹¤¼þÀàÐ͵ÄÃüÁÀýÈç¿ÉÒÔʹÓà create-controller,create-domain-classµÈµÈ¡£
ΪÁË·½±ã¿ÉÒÔʹÓÃIDE»òÕ߯äËûÄãϲ»¶µÄ±à¼­Æ÷À´´´½¨
±ÈÈç˵£¬ÎªÁË´´½¨Ò»¸öÓ¦ÓõĻù´¡£¬ÄãÖÁÉÙÐèÒªÒ»¸öÁìÓòÄ£ÐÍ£¬ÃüÁîÈçÏ£º
grails create-domain-class book

ÕâÑù»áÔÚgrails-app/domain/Book.groovyÖд´½¨Ò»¸öÁìÓòÀ࣬ÆäÄÚÈÝÈçÏÂ:

class Book {	
}

¸ü¶àµÄcreate-*ÃüÁî¿ÉÒÔÔڲο¼Ö¸ÄϵÄÃüÁîÐÐÖб»Ì½Ë÷¡£

2.11 Éú³ÉGrailsÓ¦ÓÃ

ÔÚ´´½¨ÍêGrailsÓ¦Óúóͨ³£»áʹÓá°½ÅÊּܡ±À´Éú³ÉÕû¸öÓ¦ÓóÌÐòµÄ¹Ç¼Ü¡£ÕâÊÇͨ¹ýʹÓÃgenerate-*ÃüÁîÀ´Íê³ÉµÄ£¬ÀýÈçʹÓÃgenerate-allÃüÁîÀ´¸ù¾ÝÁìÓòÄ£ÐÍÉú³É¿ØÖÆÆ÷¼°ÆäÏàÓ¦ÊÓͼ¡£ÓÉÓÚ֮ǰÎÒÃÇ´´½¨ÁËÒ»¸öBook.groovyµÄÁìÓòÄ£ÐÍ£¬Òò´ËÔÚÕâÀïÔËÐÐÈçÏÂÃüÁî¡£
grails generate-all Book

¸ü¶à¹ØÓÚ½ÅÊּܵÄÄÚÈÝÇë²Î¿¼Óû§Ö¸Äϵĺó±ßÕ½ڡ£

3. ÅäÖÃ

Ò²ÐíÔÚÕâÀï̸ÂÛÅäÖöÔÓÚÒ»¸ö×ñÑ­¡°¹æÔ¼ÓÅÓÚÅäÖᱵĿò¼ÜÀ´Ëµ£¬»áÈÃÈ˸е½±È½ÏÆæ¹Ö£¬µ«ÊÇʵ¼ÊÉÏÎÒÃÇÕâÀïËù˵µÄÅäÖÃÊÇÁ½¸ö²»Í¬µÄ¸ÅÄÇë²»Òª»ìÏý¡£

ʵ¼ÊÉÏGrailsµÄĬÈÏÅäÖÃÒѾ­×ãÒÔÎÒÃǽøÐпª·¢£¬²¢ÇÒËüÄÚÖÃÁËÈÝÆ÷ºÍÄÚ´æÄ£Ê½µÄHSQLÊý¾Ý¿â£¬ÕâÑùÎÒÃǼ¸ºõÁ¬Êý¾Ý¿â¶¼²»ÓÃÅäÖÃÁË¡£

²»¹ý£¬ÔÚ½«À´Äã¿Ï¶¨ÊÇÏëÒªÅäÖÃÒ»¸öÕæÕýµÄÊý¾Ý¿âµÄ£¬ÏÂÃæµÄÕ½ڽ«½éÉÜÈçºÎʵÏÖ¡£

3.1 »ù±¾ÅäÖÃ

GrailsÌṩÁËÒ»¸ögrails-app/conf/Config.groovyÅäÖÃÎļþ£¬ÓÃÀ´Íê³ÉÒ»°ãͨ³£µÄÅäÖá£Õâ¸öÎļþʹÓ÷dz£ÀàËÆÓÚJavaÊôÐÔ£¨propertiese£©Îļþ¡¢²»¹ýÊÇ´¿GroovyµÄConfigSlurper £¬ÕâÑù¾Í¿ÉÒÔÖØÓã¨re-use£©±äÁ¿ÒÔ¼°Ö¸¶¨ºÏÊʵÄJavaÀàÐÍ¡£

ÀýÈ磬¿ÉÒÔÔÚÎļþÖмÓÉÏ×Ô¼º¶¨ÒåµÄÅäÖÃÐÅÏ¢¡£

foo.bar.hello = "world"

ÕâÑùÔÚÓ¦ÓóÌÐòÖÐÓÐÁ½ÖÖ·½Ê½¿ÉÒÔ·ÃÎÊÕâЩÅäÖÃÐÅÏ¢¡£×î³£ÓõÄÒ»ÖÖ·½Ê½ÊÇͨ¹ýGrailsApplication¶ÔÏó£¬ËüÔÚ¿ØÖÆÆ÷ºÍ±êÇ©¿âÖж¼¿ÉÒÔ×÷Ϊ±äÁ¿À´Ê¹Óá£

assert "world" == grailsApplication.config.foo.bar.hello

3.1.1 ÄÚÖÃÑ¡Ïî

GrailsͬÑùÌṩÁËÈçÏÂÅäÖÃÑ¡Ï

3.1.2 ÈÕÖ¾

ÈÕÖ¾»ù´¡

Grailsͨ¹ý×ÔÉíµÄÅäÖûúÖÆÀ´ÅäÖÃLog4j ÈÕ־ϵͳ¡£ÒªÊ¹ÓÃÈÕÖ¾¼Ç¼¹¦ÄÜ£¬ÐèÒªÐÞ¸ÄλÓÚgrails-app/confĿ¼ÏµÄConfig.groovyÎļþ¡£ÔÚÕâ¸öÎļþÖпÉÒÔÖ¸¶¨²»Í¬»·¾³£¬ÀýÈçdevelopment,testºÍproduction»·¾³ÏµÄÈÕÖ¾·½Ê½¡£Grails»á×Ô¶¯´¦Àí¸ÃÎļþ²¢ÔÚweb-app/WEB-INF/classesĿ¼ÏÂÉú³ÉÏàÓ¦µÄlog4j.propertiesÎļþ¡£

ÀýÈ磬ÔÚGrailsÖпÉÒÔ°´ÕÕÈçÏ·½Ê½ÅäÖÃLog4j£º

log4j {
    appender.stdout = "org.apache.log4j.ConsoleAppender"
	appender.'stdout.layout'="org.apache.log4j.PatternLayout"
    rootLogger="error,stdout"
    logger {
        grails="info,stdout"
        org {
            grails.spring="info,stdout"
            codehaus.groovy.grails.web="info,stdout"
            codehaus.groovy.grails.commons="info,stdout"
            …
        }
}

Èç¹ûÏëʹÓÃLog4jÎļþµÄ±ê×¼·ç¸ñ½øÐÐÅäÖ㬿ÉÒÔʹÓÃGroovyÖеĶàÐÐ×Ö·û´®£¬ÀýÈ磺

log4j = '''
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
# ...remaining configuration
'''

ÆäËûһЩÓÐÓõÄÈÕÖ¾¼Ç¼Æ÷°üÀ¨£º

ÍêÕûµÄÐÅÏ¢¸ú×Ù

µ±Òì³£·¢ÉúµÄʱºò£¬¿ÉÄÜÓÉÓÚJavaºÍGroovyÄÚ²¿µÄ´íÎó¶ø²úÉúÒ»´ó¶ÑµÄÎÞÓÃÐÅÏ¢¡£Grails¶ÔÕâЩ²»ÐèÒªµÄÐÅÏ¢½øÐÐÁ˹ýÂË£¬±£Ö¤¶Ô·ÇGrails/GroovyºËÐÄÀà°üµÄÐÅÏ¢¸ú×Ù¡£

ͨ³£ÕâЩÐÅÏ¢¶¼»áͨ¹ýStackTraceÈÕÖ¾¼Ç¼Æ÷¼Ç¼µ½stacktrace.logÎļþÖС£²»¹ýͨ¹ýÐÞ¸ÄConfig.groovy¿ÉÒԸıäÆä¼Ç¼·½Ê½£¬ÀýÈçÈç¹ûÒª½«¸ú×ÙÐÅÏ¢Ö±½ÓÊä³öµ½±ê×¼Êä³ö£¨standard out£©£¬ÐèÒª½«ÈçÏÂÅäÖÃ:

StackTrace="error,errors"

ÐÞ¸ÄΪ:

StackTrace="error,stdout"

Ò²¿ÉÒÔͨ¹ýÉèÖÃgrails.full.stacktraceÐéÄâ»úÊôÐÔΪtrueÀ´½ûÓöԸú×ÙÐÅÏ¢µÄ¹ýÂË£¬ÀýÈç

grails -Dgrails.full.stacktrace=true run-app

°´ÕÕ¹æÔ¼µÄÈÕÖ¾¼Ç¼

ËùÓеÄÓ¦ÓóÌÐò¹¤¼þ¶¼¶¯Ì¬µØÔö¼ÓÁËlogÊôÐÔ£¬ÆäÖаüÀ¨ÁìÓòÄ£ÐÍ£¨Domain Class£©£¬¿ØÖÆÆ÷ÒÔ¼°±êÇ©¿âµÈµÈ¡£Ê¹Ó÷½·¨ÈçÏÂËùʾ:

def foo = "bar"
log.debug "The value of foo is $foo"

ÔÚGrailsÖÐʹÓÃgrails.app.<artefactType>.ClassNameµÄÃüÃû¹æÔ¼À´ÃüÃûÈÕÖ¾¼Ç¼Æ÷¡£ÀýÈç

# Set level for all application artefacts
log4j.logger.grails.app="info, stdout"

# Set for a specific controller log4j.logger.grails.app.controller.YourController="debug, stdout"

# Set for a specific domain class log4j.logger.grails.app.domain.Book="debug, stdout"

# Set for a specific taglib log4j.logger.grails.app.tagLib.FancyAjax="debug, stdout"

# Set for all taglibs log4j.logger.grails.app.tagLib="info, stdout"

ÆäÖеŤ¼þÃû£¨¼´<artefactType>£©Ò²Êǰ´ÕÕ¹æÔ¼À´ÃüÃûµÄ£¬Ò»Ð©³£¼ûµÄÈçÏÂËùʾ:

3.2 »·¾³

¸÷¸ö»·¾³µÄÅäÖÃ

GrailsÖ§³ÖÕë¶Ô²»Í¬µÄ»·¾³½øÐв»Í¬µÄÅäÖã¬ÕâÑù²»¹ÜÊÇConfig.groovyÎļþ»¹ÊÇgrails-app/confĿ¼ÏµÄDataSource.groovyÎļþ£¬¶¼¿ÉÒÔ½èÖúÓÚConfigSlurper À´ËæÊ±¸Ä±äËù´¦µÄ»·¾³¡£ÀýÈ磬ĬÈÏGrailsÖеÄDataSource¶¨ÒåÈçÏ£º

dataSource {
    pooling = false                          
    driverClassName = "org.hsqldb.jdbcDriver"	
    username = "sa"
    password = ""				
}
environments {
    development {
        dataSource {
            dbCreate = "create-drop" // one of 'create', 'createeate-drop','update'
            url = "jdbc:hsqldb:mem:devDB"
        }
    }   
    test {
        dataSource {
            dbCreate = "update"
            url = "jdbc:hsqldb:mem:testDb"
        }
    }   
    production {
        dataSource {
            dbCreate = "update"
            url = "jdbc:hsqldb:file:prodDb;shutdown=true"
        }
    }
}

×¢Ò⣬ÔÚÎļþµÄ×ʼÌṩÁËÒ»¸öͨÓõÄÅäÖã¬È»ºóÔÚenvironments´úÂë¿éÖÐÕë¶Ô²»Í¬µÄ»·¾³ÏµÄDataSource£¬ÅäÖÃÁ˶ÔÓ¦µÄdbCreateºÍurlÊôÐÔ¡£ÔÚConfig.groovyÖÐÒ²¿ÉÒÔʹÓÃÕâÑùµÄÓï·¨¡£

´ò°üÒÔ¼°Çл»»·¾³

grailsµÄÃüÁîÐÐÖпÉÒÔÖ¸¶¨ÔËÐÐʱµÄ»·¾³£¬ÐÎʽÈçÏ£º

grails [environment] [command name]

ÆäÖеÄenvironment²ÎÊý·Ö±ðÓÃdev,prod,testÖ¸´ú¿ª·¢»·¾³£¨development£©£¬²úÆ·»·¾³£¨production£©ÒÔ¼°²âÊÔ»·¾³£¨test£©£¬ÀýÈçÎÒÃÇÏëÔÚ²âÊÔ»·¾³Öд´½¨warÎļþ£¬¿ÉÒÔÖ´ÐÐÈçÏÂÃüÁ

grails test war

Èç¹û×Ô¶¨ÒåÁËÆäËûµÄ»·¾³£¬¿ÉÒÔÔÚÃüÁîÐÐÖÐͨ¹ýgrails.env²ÎÊýÖ¸¶¨»·¾³£¬ÀýÈç:

grails -Dgrails.env=UAT run-app

ÔÚ³ÌÐòÖмì²â»·¾³

ÔÚGant½Å±¾»òÕ߯ô¶¯ÀàÖУ¬¿ÉÒÔͨ¹ýGrailsUtilÀàÀ´ÅжÏÔËÐеĻ·¾³£¬ÀýÈ磺

import grails.util.GrailsUtil

...

switch(GrailsUtil.environment) { case "development": configureForDevelopment() break case "production": configureForProduction() break }

3.3 Êý¾ÝÔ´

ÓÉÓÚGrailsÊÇ»ùÓÚJavaÖ®Éϵģ¬ËùÒÔÐèÒª¶ÁÕßÓÐÒ»¶¨µÄ¹ØÓÚJDBCµÄ֪ʶ¡£

Ê×ÏÈ£¬Èç¹ûҪʹÓÃHSQLÖ®ÍâÆäËüµÄÊý¾Ý¿â£¬ÐèÒªÓÐÏàÓ¦µÄJDBC Driver£¨Çý¶¯£©£¬ÀýÈç¶ÔÓÚMySQLÐèÒªÓÐ Connector/J

ÕâЩÊý¾Ý¿âÇý¶¯Ò»°ã¶¼ÊÇJARµÄÐÎʽ·¢²¼£¬Ö»ÐèÒª°ÑÕâЩJARÎļþ·Åµ½¹¤³ÌϵÄlibĿ¼Ï¼´¿É¡£

ÔÚ·ÅÖÃÍêJDBCÇý¶¯ºó£¬ÎÒÃÇÐèÒªÊìϤÒÔÏÂλÓÚgrails-app/conf/ϵÄGrailsÊý¾Ý¿âÅäÖÃÎļþDataSoruce.groovy¡£³õʼ״̬ϸÃÎļþ»á°üÀ¨ÈçÏÂÑ¡Ïî:

Ò»¸öµäÐ͵ÄMySQLµÄÅäÖÃÈçÏ£º

                             
dataSource {
	pooling = true
	dbCreate = "update"
	url = "jdbc:mysql://localhost/yourDB"
	driverClassName = "com.mysql.jdbc.Driver"
	username = "yourUser"
	password = "yourPassword"	
}

3.3.1 Êý¾ÝÔ´ºÍ»·¾³

֮ǰµÄʾÀýǰÌáÊǼÙÉèÎÒÃÇÔÚdevelopment,productionºÍtestµÈ»·¾³ÏµÄÅäÖÃÒ»Ñù¡£

²»¹ýGrailsÖеÄÊý¾ÝÔ´ÊÇ¿ÉÒÔ¸ù¾Ý²»Í¬»·¾³À´½øÐв»Í¬µÄÅäÖã¬ÀýÈ磺

dataSource {
	// common settings here
}                     
environments {
  production {
     dataSource {
          url = "jdbc:mysql://liveip.com/liveDb"					
     }			
  }
}

3.3.2 JNDIÊý¾ÝÔ´

ÓÉÓÚJ2EEÈÝÆ÷Ò»°ã¶¼Ö§³Ö¸ù¾ÝJava Naming and Directory Interface (JNDI)À´²éÕÒÊý¾ÝÔ´¡£

ËùÒÔGrailsÒ²ÌṩÁ˸ù¾ÝJNDIÀ´ÅäÖÃÊý¾ÝÔ´µÄ·½Ê½,ʾÀýÈçÏ£º


dataSource {
    jndiName = "java:comp/env/myDataSource"
}

ËäÈ»J2EEÈÝÆ÷¼ä¶¨ÒåJNDIÃû³Æ¸ñʽ²î±ðºÜ´ó£¬µ«ÊÇÄ㶨ÒåDataSourceµÄ·½Ê½ÒÀ¾ÉÊÇÏàͬµÄ¡£

3.3.3 ×Ô¶¯Êý¾Ý¿âÒÆÖ²

DataSource.groovyÎļþÖеÄdbCreateÊôÐÔÊ®·ÖÖØÒª£¬ÒòΪËü¿ÉÒÔÓÃÀ´Ö¸¶¨ÊÇ·ñ×Ô¶¯¸ù¾ÝGORMÀàÀ´´´½¨Êý¾Ý¿â±í¡£Æä¿ÉѡֵΪ£º

¿ª·¢»·¾³£¨development£©ÏÂdbCreateÊôÐÔĬÈÏÉèÖÃΪcreate-drop:

dataSource {
	dbCreate = "create-drop" // one of 'create', 'create-drop','update'
}

ÕâÑùÔÚGrailsÓ¦ÓóÌÐòÆô¶¯µÄʱºò»áɾ³ýµôÔ­À´µÄÊý¾Ý¿â²¢ÖØÐ½¨Á¢£¬ÔÚproduction»·¾³ÖÐͨ³£ÐèÒªÐ޸ĸÃÖµ¡£

ËäÈ»GrailsÔÝʱ²»Ö§³ÖRails·½Ê½µÄÒÆÖ²£¬²»¹ý¿ÉÒÔͨ¹ý°²×°LiquiBaseºÍDbMigrateÁ½¸ö²å¼þÀ´ÊµÏÖ£¬ÔÚ¿ØÖÆÌ¨ÖпÉÒÔͨ¹ýgrails list-pluginsÃüÁîÀ´²é¿´ÕâÁ½¸ö²å¼þ¡£

3.4 ÍⲿÅäÖÃ

¶ÔÓÚ´ó¶àÊýÇé¿öÀ´Ëµ£¬Ê¹ÓÃλÓÚgrails-app/confĿ¼ÏµÄDataSource.groovyÎļþÖеÄĬÈÏÅäÖÃÒѾ­¹»ÓÃÁË¡£²»¹ýÓÐЩÇé¿öÏÂÐèÒªÔÚ¹¤³ÌÏîÄ¿ÒÔÍâÀ´±£´æÕâЩÅäÖá£ÀýÈçÔÚ´ò°üWARʱ£¬ÓÐʱºòÐèÒª½«ÅäÖÃÎļþ·ÅÖÃÓÚ¹¤³ÌÖ®ÍâÀ´±ÜÃâÓÉÓÚÅäÖõÄÐ޸ĵ¼ÖÂWAR°üµÄÖØÐ´ò°ü¡£

ΪÁËÖ§³ÖÕâÖÖÍⲿÅäÖã¬ÐèÒªÔÚConfig.groovyÎļþÖÐÊ×ÏÈÖ¸¶¨grails.config.locationsÑ¡ÏÒÔ±ã¸æËßGrailsÓ¦¸Ãµ½ºÎ´¦È¥¼ÓÔØÍⲿÅäÖÃÎļþ¡£

grails.config.locations = [ "classpath:${appName}-config.properties",
                            "classpath:${appName}-config.groovy",
                            "file:${userHome}/.grails/${appName}-config.properties",
                            "file:${userHome}/.grails/${appName}-config.groovy"]

ÔÚÉÏÀýÖУ¬ÎÒÃǼÓÔØÁ˲»Í¬classpathÏÂÒÔ¼°USER_HOMEϵÄÅäÖÃÎļþ£¬°üÀ¨JavaÊôÐÔÎļþºÍConfigSlurper ÅäÖÃÎļþ¡£

×îÖÕ¿ÉÒÔͨ¹ýGrailsApplication¶ÔÏóµÄconfigÊôÐÔÀ´»ñµÃÕâЩÅäÖÃÎļþÖеÄÖµ¡£

Grails»¹Ö§³ÖSpring ÖеÄÊôÐÔռ루property place holders£©ºÍÊôÐÔÖØÔØ£¨property override£©ÅäÖ㬹ØÓÚÕâЩ·½ÃæµÄÏêϸÄÚÈÝÇë²Î¿¼GrailsºÍSpringÕ½ڡ£

3.5 ¶¨Òå°æ±¾

°æ±¾¶¨Òå»ù´¡

GrailsÄÚÖÃÖ§³ÖÓ¦ÓóÌÐòµÄ°æ±¾¶¨Òå¡£µ±Ê¹ÓÃcreate-app´´½¨Ò»¸öÓ¦ÓóÌÐòµÄʱºò£¬¸ÃÓ¦ÓóÌÐòµÄ°æ±¾¼´±»ÉèÖÃΪ0.1¡£°æ±¾ÊÇ´æ´¢ÔÚÏîÄ¿ÎļþµÄ¸ùĿ¼ÏµÄapplication.propertiesÔªÎļþÖС£

Òª¸Ä±äÓ¦ÓóÌÐòµÄ°æ±¾¿ÉÒÔʹÓÃset-versionÃüÁÀýÈ磻

grails set-version 0.2

GrialsÔںܶàµÄÃüÁîÖж¼»áʹÓõ½°æ±¾£¬ÀýÈçwarÃüÁî¾Í»áÔÚÉú³ÉµÄWARÎļþ×îºó¼ÓÉÏÓ¦ÓóÌÐòµÄ°æ±¾¡£

ÔËÐÐʱÅжϰ汾

ÓÉÓÚGrailsÖ§³ÖÓ¦ÓóÌÐòÔªÊý¾Ý£¨metadata£©£¬ËùÒÔ¿ÉÒÔͨ¹ýGrailsApplicationÀàÀ´µÃµ½µ±Ç°µÄ°æ±¾ÐÅÏ¢¡£ÀýÈçÔÚ¿ØÖÆÆäÖпÉÒÔʹÓÃÄÚÖõÄgrailsApplication±äÁ¿¡£

def version = grailsApplication.metadata['app.version']

Èç¹ûÏë²é¿´GrailsµÄ°æ±¾£¬¿ÉÒÔÓÐÁ½ÖÖ·½Ê½£º

def grailsVersion = grailsApplication.metadata['app.grails.version']

»òÕß GrailsUtilÀࣺ

import grails.util.*
def grailsVersion = GrailsUtil.grailsVersion

4. ÃüÁîÐÐ

GrailsµÄÃüÁîÐÐÊÇ»ùÓÚGant--ÓÃGroovy¶ÔApache Ant½øÐеļòµ¥·â×°Ö®Éϵġ£

²»¹ýGrailsͨ¹ýʹÓùæÔ¼ÒÔ¼°grailsÃüÁî½øÒ»²½¶ÔÆä½øÐÐÁËÀ©Õ¹¡£µ±ÊäÈë:

grails [command name]
ʱ£¬Grails»áÔÚÒÔÏÂĿ¼ÖÐËÑË÷ÏàÓ¦µÄGant½Å±¾²¢Ö´ÐУº

Grails»á×Ô¶¯½«ÃüÁîÃû³Æ´ÓСдÐÎʽת»»³ÉÍÕ·åÐÎʽ£¨camel case£©À´²éÕÒÏàÓ¦½Å±¾Îļþ£¬ÀýÈçÈç¹ûÊäÈë:

grails run-app

ÄÇôGrails»áÈ¥ËÑË÷ÒÔϽű¾Îļþ£º

Èç¹ûÓжà¸öÆ¥ÅäÎļþ£¬Grails»áÈÃÄãÑ¡ÔñÆäÖеÄÒ»¸öÀ´Ö´ÐС£µ±Gant½Å±¾Ö´ÐеÄʱºò£¬¡°default¡±Ä¿±ê£¨Target£©Ò²»áһִͬÐС£

ÒªÏë»ñµÃÓÐЧÃüÁîµÄÇåµ¥£¬¿ÉÒÔÊäÈë:

grails help

ÕâÑù»áÁгöGrailsÖпÉÒÔʹÓÃÃüÁîÇåµ¥£¬È磺

Usage (optionals marked with *): 
grails [environment]* [target] [arguments]*

Examples: grails dev run-app grails create-app books

Available Targets (type grails help 'target-name' for more info): grails bootstrap grails bug-report grails clean grails compile ...

¹ØÓÚµ¥¸öÃüÁîµÄ¸ü¶àÏêϸÐÅÏ¢Çë²Î¿¼×ó±ß²Ëµ¥µÄÃüÁîÐÐÖ¸ÄÏ¡£

4.1 ´´½¨Gant½Å±¾

ͨ¹ýÔÚÏîÄ¿µÄ¸ùĿ¼ÏÂÔËÐÐcreate-scriptÃüÁî¿ÉÒÔ´´½¨Gant½Å±¾¡£ÀýÈ磺
grails create-script compile-sources

ÕâÌõÃüÁî»áÔÚscriptsĿ¼Ï´´½¨Ò»¸öÃûΪCompileSources.groovyµÄ½Å±¾Îļþ¡£Gant½Å±¾±¾ÉíÀàËÆÓÚGroovy´úÂ룬µ«ÊÇËü¿ÉÒÔÖ§³ÖÄ¿±ê£¨Target£©ÒÔ¼°ËüÃÇÖ®¼äµÄÒÀÀµ¹ØÏµ¡£

target(default:"The default target is the one that gets executed by Grails") {
	depends(clean, compile)
}
target(clean:"Clean out things") {
	Ant.delete(dir:"output")
}
target(compile:"Compile some sources") {
	Ant.mkdir(dir:"mkdir")
	Ant.javac(srcdir:"src/java", destdir:"output")
}

´ÓÈçÉϽű¾ÖпÉÒÔ¿´³ö£¬ÆäÖÐÓÐÒ»¸öĬÈÏ¿ÉÒÔ·ÃÎÊApache Ant APIµÄAnt±äÁ¿¡£

ÈçÉÏdefaultÄ¿±êÖÐËùʹÓõÄÒ»Ñù£¬ÄãÒ²¿ÉÒÔʹÓÃdepends·½·¨À´Ö¸¶¨ÆäÒÀÀµµÄÆäËûÄ¿±ê¡£

4.2 ¿É¸´ÓõÄGrails½Å±¾

GrailsµÄÃüÁîÐÐÖл¹¿ÉÒÔÖØÓÃÐí¶àÓÐÓõĽű¾´úÂë(²é¿´ËùÓÐÃüÁîµÄÈëÃÅÖ¸ÄÏÇë²Î¿¼ÃüÁîÐÐÖ¸ÄÏ)£¬ÆäÖÐ×îÖ÷ÒªµÄÓÐcompile, packageºÍbootstrap½Å±¾¡£

ÒÔbootstrap½Å±¾ÎªÀý£¬ÒÔÏ´úÂë¿ÉÒÔÔÚÆô¶¯Ê±µ÷ÓÃSpringÖеÄApplicationContextʵÀýÀ´·ÃÎÊÊý¾ÝÔ´£º

Ant.property(environment:"env")                             
grailsHome = Ant.antProject.properties."env.GRAILS_HOME"

includeTargets << new File ( "${grailsHome}/scripts/Bootstrap.groovy" )

target ('default': "Load the Grails interactive shell") { depends( configureProxy, packageApp, classpath, loadApp, configureApp )

Connection c try { // do something with connection c = appCtx.getBean('dataSource').getConnection() } finally { c?.close() } }

4.3 ½Å±¾ÖеÄʼþ

GrailsÌṩÁ˵÷Óýű¾Ê¼þµÄÄÜÁ¦£¬ÕâЩʼþÊÇÔÚGrailsÄ¿±ê£¨target£©ºÍ²å¼þ½Å±¾Ö´ÐÐʱËù´¥·¢µÄ¡£

´Ë»úÖÆ×ÚÖ¼ÊǼòµ¥ºÍ×ÔÓÉ£¬Òò´ËÎÞ·¨µÃµ½ËùÓпÉÄÜ´¥·¢Ê¼þµÄÁÐ±í¡£ÕâÑùµ±Ã»ÓкËÐÄÄ¿±ê½Å±¾´¥·¢Ê¼þµÄʱºò£¬¾ÍÓпÉÄܵ÷Óòå¼þ½Å±¾Ëù´¥·¢µÄʼþ¡£

¶¨Òåʼþ¾ä±ú

ʼþ¾ä±úÊÇÔÚscripts/Ŀ¼Ï£¨²å¼þ½Å±¾£©»òÕßUSER_HOMEĿ¼ÏµÄ.grails/scripts/Ŀ¼ÏµÄEvents.groovy½Å±¾Öж¨ÒåµÄ¡£µ±Ò»¸öʼþ´¥·¢µÄʱºò»áµ÷ÓÃËùÓеÄʼþ½Å±¾£¬ËùÒÔ¿ÉÒÔʹÓÃ10¸ö²å¼þ½Å±¾»òÕßʹÓÃÒ»¸öÓû§×Ô¶¨Òå½Å±¾À´´¦Àíʼþ¡£

ÔÚEvents.groovyÖÐÊÇͨ¹ýÒÔ¡°event¡±¿ªÍ·ÃüÃûµÄ´úÂë¿éÀ´¶¨ÒåʼþµÄ¡£ÈçÏÂÀýËùʾ£¬Ç뽫ÏÂÀý·ÅÈë/scriptsĿ¼¡£

eventCreatedArtefact = { type, name ->
   println "Created $type $name"
}

eventStatusUpdate = { msg -> println msg }

eventStatusFinal = { msg -> println msg }

¿ÉÒÔ¿´µ½ÆäÖж¨ÒåÁËÈý¸ö¾ä±úeventCreatedArtefact,eventStatusUpdateºÍeventStatusFinal¡£GrailsÒ²ÌṩÆäËûһЩ±ê×¼µÄʼþ£¬¿ÉÒÔÔÚÃüÁîÐвο¼Ö¸ÄÏÖÐÕÒµ½¡£ÀýÈ磬compileÃüÁî»á´¥·¢ÈçÏÂʼþ¡£

´¥·¢Ê¼þ

ÈçÏÂÀýËùʾ£¬Òª´¥·¢Init.groovy½Å±¾µÄʼþÖ»ÐèÒýÈëËü²¢ÇÒµ÷ÓÃevent()±Õ°ü¡£

Ant.property(environment:"env")
grailsHome = Ant.antProject.properties."env.GRAILS_HOME"
includeTargets << new File ( "${grailsHome}/scripts/Init.groovy" )

event("StatusFinal", ["Super duper plugin action complete!"])

³£ÓÃʼþ

ϱíÁгöÁËһЩ³£ÓõÄʼþ£º
ʼþ²ÎÊýÃèÊö
StatusUpdatemessage´«ÈëµÄ×Ö·û´®±íÃ÷µ±Ç°½Å±¾µÄ״̬/½ø¶È
StatusErrormessage´«ÈëµÄ×Ö·û´®±íÃ÷µ±Ç°½Å±¾µÄ´íÎóÐÅÏ¢
StatusFinalmessage´«ÈëµÄ×Ö·û´®±íÃ÷×îºó½Å±¾µÄ״̬ÐÅÏ¢£¬±ÈÈçµ±Ò»¸öÄ¿±ê/ÈÎÎñ£¨target£©Íê³Éʱ£¬¼´±ãÊǽű¾»·¾³Öв¢²¢´æÔÚÄ¿±ê£¬ÕÕÑù¿ÉÒÔÏÔʾ״̬ÐÅÏ¢
CreatedArtefactartefactType,artefactNameÔÚÒ»¸öcreate-xxxx½Å±¾Íê³É²¢ÇÒ´´½¨ÁËÒ»¸ö¹¤¼þµÄʱºòµ÷ÓÃ
CreatedFilefileNameÔÚÒ»¸ö¹¤³ÌµÄÔ´Îļþ±»´´½¨µÄʱºòµ÷Ó㬵«²»°üÀ¨±»Grails¹ÜÀíµÄÄÇЩ³£Á¿Îļþ
ExitingreturnCodeÔڽű¾»·¾³¼´½«ÍêÈ«ÖÕÖ¹µÄʱºò±»µ÷ÓÃ
PluginInstalledpluginNameÔÚÒ»¸ö²å¼þ±»°²×°ÒÔºó±»µ÷ÓÃ
CompileStartkindÔÚ±àÒ뿪ʼµÄʱºòµ÷Ó㬴«ÈëµÄ²ÎÊýÊDZàÒëµÄÔ´Îļþ»òÕß²âÊÔÎļþµÄÖÖÀà
CompileEndkindÔÚ±àÒë½áÊøµÄʱºòµ÷Ó㬴«ÈëµÄ²ÎÊýÊDZàÒëµÄÔ´Îļþ»òÕß²âÊÔÎļþµÄÖÖÀà
DocStartkindÔÚÉú³ÉÎĵµ¼´½«¿ªÊ¼µÄʱºòµ÷ÓðüÀ¨javadoc»òÕßgroovydoc
DocEndkindÔÚÉú³ÉÎĵµ½áÊøµÄʱºòµ÷ÓðüÀ¨javadoc»òÕßgroovydoc
SetClasspathrootLoaderÔÚclasspath³õʼ»¯Æä¼äµ÷Óã¬ÕâÑù²å¼þ¾Í¿ÉÒÔʹÓÃrootLoader.addURL(...)À´ÉèÖÃclasspath²ÎÊý¡£×¢Ò⣡´ËÉèÖÃclasspath²ÎÊýÊÇÔÚʼþ½Å±¾¼ÓÔØÖ®ºó£¬Òò´ËÄã²»ÄÜÓôËclasspathÀ´¼ÓÔØËùÐèµÄÀ࣬¼´±ãÊÇÄãÔÚʼþ½Å±¾ÖÐÒѾ­µ¼È룬µ«ÊÇÈç¹ûÄãÊÇͨ¹ýÃû×ÖÀ´¼ÓÔØÀ࣬ÄÇÊÇ¿ÉÒԵġ£
PackagingEndnoneÔÚ´ò°üÍêÍê±ÏµÄʱºòµ÷Óã¨ÔÚJetty·þÎñÆ÷Æô¶¯Ö®Ç°£¬µ«ÔÚweb.xmlÉú³ÉÖ®ºó£© at the end of packaging (which is called prior to the Jetty server being started and after web.xml is generated)
ConfigureJettyJetty Server objectÔÚJetty web·þÎñÆ÷ÅäÖóõʼ»¯Ö®ºóµ÷ÓÃ

4.4 AntºÍMaven

Ant¼¯³É

µ±Í¨¹ýcreate-appÃüÁî´´½¨ÁËÒ»¸öGrailsÓ¦ÓÃʱ£¬Grails»á×Ô¶¯´´½¨Ò»¸öApache Ant µÄbuild.xmlÎļþ£¬ÆäÖаüÀ¨ÒÔÏÂÄ¿±ê£º

Õ⼸¸öÄ¿±ê¶¼¿ÉÒÔͨ¹ýAntÀ´ÔËÐУ¬Èç:

ant war

Õâ¸öbuild.xmlÎļþ¿ÉÒÔµ÷ÓÃGrailsÖеÄÃüÁÕâÑù¾Í¿ÉÒÔ½«GrailsÓ¦ÓÃÓë³ÖÐø¼¯³É·þÎñÆ÷£¬ÈçCruiseControl ºÍHudson ¼¯³ÉÆðÀ´¡£

Maven ¼¯³É

GrailsÔÝʱ²»Ö§³ÖMaven £¬µ«ÊÇÒѾ­ÓÐÁËÒ»¸öÃûΪMaven Tools for Grails µÄÏîÄ¿£¬Ëü¿ÉÒÔ¶Ôµ±Ç°µÄGrailsÓ¦Óô´½¨Ò»¸öPOM£¬ÕâÑù¾Í¿ÉÒÔ½«Grails¼¯³Éµ½Maven µÄÉúÃüÖÜÆÚÖС£

Òª»ñµÃ¸ü¶àÐÅÏ¢Çë·ÃÎÊMaven Tools for Grails ÏîÄ¿ÍøÕ¾

5. ¶ÔÏó¹ØÏµÓ³Éä(GORM)

ÁìÓòÀàÊÇÈκÎÉÌÒµÓ¦ÓõĺËÐÄ£¬ËüÃDZ£´æÕâÕâЩÉÌÒµ¹ý³ÌµÄ״̬²¢ÇÒʵÏÖÏàÓ¦µÄÐÐΪ£¬ËüÃÇ»¹Í¨¹ýÒ»¶ÔÒ»»òÕßÒ»¶Ô¶àµÄ¹ØÏµÏ໥ÁªÏµÔÚÒ»Æð¡£

GORMÊÇGrailsµÄ¶ÔÏó¹ØÏµÓ³É䣨ORM£©µÄʵÏÖ£¬Êµ¼ÊÉÏËüʹÓõÄÊÇHibernate3£¨·Ç³£Á÷ÐкÍÁé»îµÄ¿ªÔ´ORM½â¾ö·½°¸£©£¬µ«ÒòΪÓÐGroovyµÄ¶¯Ì¬ÌØÐÔÖ§³Ö£¬Òò´ËGORM¼ÈÖ§³Ö¶¯Ì¬ÀàÐÍÒ²Ö§³Ö¾²Ì¬ÀàÐÍ£¬ÔÙ¼ÓÉÏGrailsµÄ¹æÔ¼£¬ÏÖÔÚ´´½¨GrailsµÄÁìÓòÀàÖ»ÐèÒª¸üÉÙµÄÅäÖþͿÉÒÔÁË¡£

ÄãÒ²¿ÉÒÔÓÃJavaÀàдGrailsµÄÁìÓòÀà¡£¼¯³ÉHibernateµÄÏà¹ØÕ½ڽéÉÜÁËÈçºÎʹÓÃJavaÀ´Ð´GrailsÁìÓòÀ࣬µ«ÓÖ²»Ê§¶¯Ì¬³Ö¾Ã·½·¨µÄÓÅÊÆ¡£ÒÔÏÂÊÇGORMʵ¼ùµÄÔ¤ÀÀ£º

def book = Book.findByTitle("Groovy in Action")

book .addToAuthors(name:"Dierk Koenig") .addToAuthors(name:"Guillaume LaForge") .save()

5.1 ¿ìËÙÖ¸ÄÏ

ÓÃcreate-domain-class ÃüÁî´´½¨Ò»¸ödomainÀà:
grails create-domain-class Person

Õ⽫ÔÚ grails-app/domain/Person.groovy Öд´½¨ÏÂÃæµÄÀࣺ

class Person {	
}

Èç¹ûÄãÔÚÊý¾ÝÔ´Öн« dbCreate ÊôÐÔÉèÖÃΪ"update", "create" »ò "create-drop"£¬Grails»á×Ô¶¯Éú³É»òÐÞ¸ÄÊý¾Ý¿âÖÐµÄ±í¡£

Äã¿ÉÒÔͨ¹ýÌí¼ÓÊôÐÔÀ´×Ô¶¨ÒåÀࣺ

class Person {	
	String name
	Integer age
	Date lastVisit
}

Ò»µ©ÄãÓÐÁËÒ»¸ödomainÀ࣬Äã¿ÉÒÔͨ¹ýshell»ò console ÖвÙ×ÝËü£º

grails console

Õ⽫»áΪÄãÔØÈëÒ»¸ö¿ÉÒÔÊäÈëGroovyÃüÁîµÄ½»»¥Ê½Í¼ÐνçÃæ¡£

5.1.1 »ù±¾µÄCRUD

³¢ÊÔÖ´ÐÐһЩ»ù±¾µÄCRUD (Create/Read/Update/Delete)²Ù×÷¡£

´´½¨

ÓÃGroovyµÄnew ²Ù×÷·û´´½¨Ò»¸ödomainÀàµÄʵÀý£¬ÉèÖÃËüµÄÊôÐÔ,È»ºóµ÷ÓÃsave:

def p = new Person(name:"Fred", age:40, lastVisit:new Date())
p.save()

save ·½·¨½«»áʹÓõײãµÄHibernate ORM½«ÄãµÄʵÀý³Ö¾Ã»¯µ½Êý¾Ý¿â¡£

¶ÁÈ¡

Grails×Ô¶¯ÔÚÄãµÄdomainÀàÖÐÌí¼ÓÁËÒ»¸öÒþº¬µÄidÊôÐÔ£¬Äã¿ÉÒÔÓÃËüÀ´È¡»Ø¶ÔÏó£º

def p = Person.get(1)
assert 1 == p.id

ÕâÀïʹÓÃget·½·¨£¬Ëü»á¸ù¾ÝÊý¾Ý¿â±êʶ·û´ÓÊý¾Ý¿âÖÐÈ¡»ØÒ»¸öPerson¶ÔÏó¡£

¸üÐÂ

Òª¸üÐÂÒ»¸öʵÀý£¬Ö»ÒªÉèÖÃÒ»ÏÂÊôÐÔ£¬È»ºóÒ²ÊǼòµ¥µÄµ÷ÓÃÒ»ÏÂsave£º

def p = Person.get(1)
p.name = "Bob"
p.save()

ɾ³ý

ʹÓÃdeleteÀ´É¾³ýÒ»¸öʵÀý:

def p = Person.get(1)
p.delete()

5.2 ÔÚGORMÖнøÐÐÁìÓò½¨Ä£

µ±¹¹½¨Ò»¸öGrialsÓ¦ÓõÄʱºò£¬Äã±ØÐëÊÂÏÈ¿¼ÂÇÐèÒª½â¾öµÄÎÊÌâÓò£¨problem domain£©¡£±ÈÈ磬Èç¹ûÄãÒª¹¹½¨Ò»¸öAmazon Êéµê£¬Äã¾ÍÒª¿¼ÂÇÊ飬×÷Õߣ¬¹Ë¿ÍºÍ³ö°æÉ̵ȵȡ£

ÕâЩÔÚGORMÖÐÊÇ×÷ΪGroovyÀàÀ´½¨Ä£µÄ£¬Òò´ËBookÀàÒªÓÐtitle,release date,ISBN numberµÈÊôÐÔ¡£ÔÚÏÂÃæµÄ¼¸½Ú½«ÑÝʾÔÚGORMÖÐÈçºÎΪÁìÓò½¨Ä£¡£

Òª´´½¨Ò»¸öÁìÓòÀ࣬ÔËÐÐ create-domain-class:

grails create-domain-class Book

Éú³ÉÁËgrails-app/domain/Book.groovy:

class Book {	
}

Èç¹ûÄãÏëʹÓðü£¬Äã¿ÉÒÔ°ÑBook.groovyÀàÒÆÈëdomainĿ¼ÏµÄÒ»¸ö×ÓĿ¼£¬²¢¸ù¾ÝGroovy(Ò²ÊÇJava)µÄ°ü¹æÔòÌí¼ÓÒ»¸öºÏÊʵÄpackageÉùÃ÷¡£

ÉÏÃæµÄÀཫ»áÔÚÊý¾Ý¿âÖÐ×Ô¶¯Ó³ÉäÒ»¸ö book (¸úÀàͬÃû)µÄ±í¡£Ó³É乿Ôò¿ÉÒÔͨ¹ýORMµÄDSLÀ´×Ô¶¨Òå¡£

ÏÖÔÚ£¬ÄãÓÐÁËÒ»¸öÁìÓòÀ࣬Äã¿ÉÒÔ°ÑËüµÄÊôÐÔ¶¨ÒåΪJavaÓïÑÔµÄÀàÐÍ.±ÈÈç:

class Book {
	String title
	Date releaseDate
	String ISBN
}

ÿ¸öÊôÐÔÓ³Éäµ½Êý¾Ý¿âÖеÄÒ»¸öÁУ¬ÁÐÃûµÄ¹æÔòÃüÃû¹æÔòÊÇÈ«²¿Ð¡Ð´²¢ÓÃÏ»®Ïß·Ö¸ô¡£±ÈÈçreleaseDateÓ³ÉäΪÁÐrelease_dateÉÏ¡£SQLÀàÐÍÊǸù¾ÝJavaÀàÐÍÀ´×Ô¶¯¼ì²âµÄ£¬ÄãÒ²¿ÉÒÔÓÃÔ¼Êø »òÕßORM DSL×Ô¶¨ÒåÓ³ÉäÀàÐÍ¡£

5.2.1 GORMÖеĹØÁª

¹ØÁª¶¨ÒåÁËdomainÀàÖ®¼äÈçºÎ»¥Ïཻ»¥¡£³ý·ÇÔÚÁ½¶Ë¶¼ÏÔʽµÄÖ¸¶¨£¬·ñÔòÒ»¸ö¹ØÁªÖ»´æÔÚÓÚ¶¨ÒåËüµÄÄǶˡ£

5.2.1.1 Ò»¶ÔÒ»

Ò»¶ÔÒ»µÄ¹ØÁªÊÇ×î¼òµ¥µÄ¹ØÁª¡£ËüÖ»Òª¶¨ÒåÒ»¸öÊôÐÔµÄÀàÐÍΪÁíÒ»¸ödomainÀà¾Í¿ÉÒÔÁË¡£¿´ÏÂÃæµÄÀý×Ó:

ʾÀý A

class Face {
    Nose nose
}
class Nose {	
}

ÕâÑùÎÒÃǾÍÓÐÁËÒ»¸ö´Ó Face µ½ NoseµÄµ¥Ïò¹ØÁª¡£Òª°ÑÕâ¸ö¹ØÁª±äΪ˫ÏòµÄ£¬Ö»ÒªÔÚÁíÒ»¶Ë¶¨ÒåÒ»ÏÂ:

ʾÀý B

class Face {
    Nose nose
}
class Nose {	
	Face face
}

ÕâÑù¾ÍÊÇË«Ïò¹ØÁªÁË¡£µ«ÊÇ£¬ÕâÖÖÇé¿öÏ£¬¹ØÁªµÄË«·½²¢²»Äܼ¶Áª¸üС£

¿´ÏÂÃæµÄ±ä»¯:

ʾÀý C

class Face {
    Nose nose
}
class Nose {	
	static belongsTo = [face:Face]
}

ÔÚÕâÀÎÒÃÇʹÓÃbelongsTo ±íʾNose "ÊôÓÚ" Face¡£Æä½á¹û¾ÍÊÇÎÒÃÇ´´½¨²¢±£´æËüʱ£¬Êý¾Ý¿â¿ÉÒÔ_¼¶Áª_¸üÐÂ/²åÈë Nose:

new Face(nose:new Nose()).save()

ÉÏÃæÕâ¸öÀý×ӻὫfaceºÍnose¶¼±£´æ¡£×¢Òâ·´ÏòµÄ²Ù×÷²¢²»¿ÉÐУ¬Ëü»áµ¼ÖÂÒ»¸ö¶ÔÓÚÁÙʱFace¶ÔÏóµÄ´íÎó:

new Nose(face:new Face()).save() // will cause an error

belongsTo µÄÁíÒ»¸öÖØÒª×÷ÓÃÊÇ£¬Èç¹ûÄãɾ³ýÁËÒ»¸öFace ʵÀý£¬ÄÇôÏà¹ØµÄNoseÒ²»á±»É¾³ý:

def f = Face.get(1)
f.delete() // both Face and Nose deleted

ûÓÐ belongsTo µÄ»°£¬½«²»»á¼¶ÁªÉ¾³ý£¬Äã»áµÃµ½Ò»¸öÍâ¼üÔ¼ÊøµÄ´íÎ󣬳ý·ÇÄãÊÖ¹¤È¥É¾³ýNose:

// error here without belongsTo
def f = Face.get(1)
f.delete()

// no error as we explicitly delete both def f = Face.get(1) f.nose.delete() f.delete()

Äã¿ÉÒÔ±£³ÖÇ°ÃæÄÇÖÖµ¥Ïò¹ØÁªµÄ¹ØÏµ£¬²¢ÔÊÐí¼¶Áª±£´æ/¸üÐÂ:

class Face {
    Nose nose
}
class Nose {	
	static belongsTo = Face
}

×¢Ò⣬ÔÚÕâÖÖÇé¿öÏ£¬ÒòΪÎÒÃÇûÓÐÔÚbelongsToÉùÃ÷ÖÐʹÓÃÓ³ÉäÓï·¨Ã÷È·µØÉùÃ÷Õâ¸ö¹ØÁª,Grails½«ÈÏΪËüÊÇÒ»¸öµ¥Ïò¹ØÁª¡£ÏÂÃæÊǶÔÕâÈý¸öÀý×ÓµÄ×ܽá:

5.2.1.2 Ò»¶Ô¶à

Ò»¶Ô¶àµÄ¹ØÏµÊÇÖ¸£¬µ±Ò»¸öÀà(±ÈÈçAuthor)ÓµÓÐÁíÒ»¸öÀà(Book)µÄ¶à¸öʵÀýÕâÖÖÇé¿ö¡£ÔÚGrailsÖУ¬Äã¿ÉÒÔʹÓà hasMany À´¶¨ÒåÕâÖÖ¹ØÁª£º

class Author {
    static hasMany = [ books : Book ]

String name } class Book { String title }

ÕâÑùÎÒÃÇÓÐÁËÒ»¸öÒ»¶Ô¶àµÄµ¥Ïò¹ØÁª¡£GrailsÔÚÊý¾Ý¿â¼¶±ð½«Ä¬ÈÏʹÓÃÍâ¼üÓ³ÉäÀ´Ó³ÉäÕâÖÖ¹ØÁª¡£

ORM DSL ÔÊÐíʹÓÃÁ¬½Ó±í×÷Ϊ´úÌæÀ´Ó³Éäµ¥Ïò¹ØÁª¡£

Grails ½«»á¸ù¾ÝhasMany ÉèÖÃΪdomainÀà×Ô¶¯×¢ÈëÒ»¸öÀàÐÍΪjava.util.SetµÄÊôÐÔ¡£Õâ¸ö¿ÉÒÔ±»ÓÃÀ´±éÀú¼¯ºÏ:

def a = Author.get(1)

a.books.each { println it.title }

GrailsʹÓõÄĬÈÏץȡ²ßÂÔÊÇ"lazy",ÕâÒâζÕ⼯ºÏ½«»á±»ÑÓ³Ù³õʼ»¯¡£Èç¹ûÄ㲻СÐĵϰ,Õâ¿ÉÄܵ¼Ö n+1 ÎÊÌâ .

Èç¹ûÄãÐèÒª"eager"ץȡ£¬Äã¿ÉÒÔʹÓÃORM DSL »òÕßÖ¸¶¨Á¢¼´×¥È¡×÷Ϊ²éѯµÄÒ»²¿·Ö¡£

ĬÈϵļ¶ÁªÐÐΪÊǼ¶Áª±£´æºÍ¸üУ¬µ«²»¼¶ÁªÉ¾³ý£¬³ý·ÇÄãÒ²Ö¸¶¨ÁË belongsTo £º

class Author {
    static hasMany = [ books : Book ]

String name } class Book { static belongsTo = [author:Author] String title }

Èç¹ûÄãÓÐÁ½¸öÏàͬÀàÐ͵ÄÊôÐÔ£¬ËûÃǶ¼ÊÇÒ»¶Ô¶à¹ØÏµµÄ¶à·½£¬Äã±ØÐëÓÃmappedByÀ´Ö¸¶¨ËûÃÇ·Ö±ðÓ³ÉäµÄÊÇÄĸö¼¯ºÏ:

class Airport {
	static hasMany = [flights:Flight]
	static mappedBy = [flights:"departureAirport"]
}
class Flight {
	Airport departureAirport
	Airport destinationAirport
}

Èç¹ûÄãÓжà¸öÓ³Éäµ½²»Í¬ÊôÐԵļ¯ºÏ£¬Ò²ÐèÒªÕâÑù:

class Airport {
	static hasMany = [outboundFlights:Flight, inboundFlights:Flight]
	static mappedBy = [outboundFlights:"departureAirport", inboundFlights:"destinationAirport"]
}
class Flight {
	Airport departureAirport
	Airport destinationAirport
}

5.2.1.3 ¶à¶Ô¶à

GrailsÖ§³Ö¶à¶Ô¶à¹ØÁª£¬ÕâÖÖ¹ØÁªÐèÒªÔÚ¹ØÁªµÄÁ½·½¶¼¶¨ÒåhasMany£¬²¢ÔÚ¹ØÁªµÄ±»ÓµÓз½¶¨ÒåbelongsTo:
class Book {
   static belongsTo = Author
   static hasMany = [authors:Author]
   String title
}
class Author {
   static hasMany = [books:Book]
   String name
}

GrialsÔÚÊý¾Ý¿â²ãʹÓÃÁ¬½Ó±íÀ´Ó³Éä¶à¶Ô¶à¹ØÁª¡£¹ØÁªµÄÓµÓз½£¬ÔÚÕâÀïÊÇAuthor,¸ºÔð³Ö¾Ã»¯Õâ¸ö¹ØÁª£¬²¢ÇÒËüÊÇΨһ¿ÉÒÔ¼¶Áª±£´æ¶Ô·½µÄÒ»·½¡£

±ÈÈçÏÂÃæµÄ´úÂë¿ÉÒÔ¹¤×÷£¬²¢»á¼¶Áª±£´æ:

new Author(name:"Stephen King")
		.addToBooks(new Book(title:"The Stand"))
		.addToBooks(new Book(title:"The Shining"))		
		.save()

µ«ÊÇÏÂÃæµÄ´úÂëÖ»±£´æ Book ¶ø²»±£´æauthors!

new Book(name:"Groovy in Action")
		.addToAuthors(new Author(name:"Dierk Koenig"))
		.addToAuthors(new Author(name:"Guillaume Laforge"))		
		.save()

ÕâÕýÊÇÎÒÃÇÆÚÍûµÄÐÐΪ£¬¸úHibernateÖÐÒ»Ñù£¬¶à¶Ô¶à¹ØÁªÖÐÖ»ÓÐÒ»·½¿ÉÒÔ¹ÜÀí¹ØÁª¡£

GrailsµÄ ½ÅÊÖ¼Ü ÌØÐÔµ±Ç°»¹²»Ö§³Ö¶à¶Ô¶à¹ØÁª£¬Òò´ËÄã±ØÐë×Ô¼ºÐ´´úÂëÀ´¹ÜÀí¹ØÁª¡£

5.2.2 GORMµÄ×éºÏ

¸ú¹ØÁªÒ»Ñù,GrailsÖ§³Ö×éºÏµÄ¸ÅÄî¡£ÔÚÕâÖÖÇé¿öÏÂ,ÒªÓ³Éäµ½Ò»¸ö¶ÀÁ¢µÄ±íµÄÀà,¿ÉÒÔǶÈëµ½µ±Ç°±í¡£±ÈÈç:

class Person {
	Address homeAddress
	Address workAddress
	static embedded = ['homeAddress', 'workAddress']
}
class Address {
	String number
	String code
}

Ó³ÉäµÄ½á¹û¿´ÆðÀ´ÏñÏÂÃæÕâÑù:

Èç¹ûÄãÔÚgrails-app/domain Ŀ¼ÏÂÒ»¸öµ¥¶ÀµÄGroovyÎļþÖж¨ÒåÁË Address À࣬Ä㻹ÊÇ»áµÃµ½Ò»¸ö address ±í¡£Èç¹ûÄã²»ÏëÕâÑù£¬Äã¿ÉÒÔʹÓÃGroovy¿ÉÒÔÔÚÒ»¸öÎļþÖж¨Òå¶à¸öÀàµÄÌØÐÔ£¬ÔÚgrails-app/domain/Person.groovy ÎļþÖÐÔÚ Person ÀàµÄÏÂÃæ¶¨Òå Address Àà¡£

5.2.3 GORMµÄ¼Ì³Ð

GORMÖ§³Ö´Ó³éÏó»ùÀàºÍ´Ó¾ßÌåʵÌåÀà¼Ì³Ð¡£±ÈÈ磺

class Content {
     String author
}
class BlogEntry extends Content {
    URL url
}
class Book extends Content {
    String ISBN
}
class PodCast extends Content {
    byte[] audioStream
}

ÔÚÉÏÃæµÄÀý×ÓÖÐÎÒÃÇÓиö½ÐContent µÄ¸¸À࣬Ȼºó²»Í¬µÄ×ÓÀàÓиü¶àËüÃǸ÷×ÔÌØ¶¨µÄÐÐΪ¡£

¼Ì³ÐÓ³ÉäµÄ˼¿¼

ÔÚÊý¾Ý¿â¼¶±ðGrailsĬÈÏʹÓÃÿ¸öÀà·Ö²ã½á¹¹Ò»¸ö±í(uses table-per-hierarchy)µÄÓ³Éä²ßÂÔ£¬ÓÐÒ»¸ö±æ±ð±êʶ(discriminator)ÁнРclass £¬Òò´Ë¸¸ÀàContent ºÍËüµÄ×ÓÀà(BlogEntry, Book µÈµÈ.)¹²Ïíͬһ¸ö±í¡£

ÿ¸öÀà·Ö²ã½á¹¹Ò»¸ö±í(uses table-per-hierarchy)ÓÐÒ»¸ö¸±×÷Ó㬾ÍÊÇÄã¼Ì³ÐÓ³ÉäÖеÄÊôÐÔ²»ÄÜÓзǿÕÔ¼Êø¡£Ò»¸öÑ¡ÔñÊÇʹÓÃÿ¸ö×ÓÀàÒ»¸ö±í(table-per-subclass)Ó³É䣬Ëü¿ÉÒÔͨ¹ýORM DSLÉèÖá£

µ«ÊÇ£¬¹ý¶ÈµÄʹÓü̳кÍÿ¸ö×ÓÀàÒ»¸ö±í(table-per-subclass)µÄÓ³Éä²ßÂÔ£¬»áµ¼Ö¹ý¶ÈʹÓÃÁ¬½Ó²éѯ£¬Ê¹µÃ²éѯÐÔÄܺܲͨ³£ÎÒÃǽ¨Òé±£³Ö¼òµ¥£¬Ö»ÓÐÔÚÄãȷʵÐèÒª¼Ì³ÐµÄʱºò²ÅÓÃËü¡£

¶à̬²éѯ

ʹÓü̳еĺô¦ÊÇÄã»ñµÃÁ˶à̬²éѯµÄÄÜÁ¦£¬±ÈÈçÔÚ³¬ÀàContent ÉÏʹÓà list ·½·¨½«»á·µ»Ø ContentµÄËùÓÐ×ÓÀà:

def content = Content.list() // list all blog entries, books and pod casts
content = Content.findAllByAuthor('Joe Bloggs') // find all by author

def podCasts = PodCast.list() // list only pod casts

5.2.4 ¼¯ºÏ¡¢ÁбíºÍÓ³Éä

¶ÔÏ󼯺Ï(Set)

µ±ÄãÔÚGROMÖж¨ÒåÒ»¸ö¹ØÁªµÄʱºò,ĬÈϵØÊ¹ÓÃjava.util.Set ,ËüÊÇÒ»¸öÎÞÐò²¢²»Äܰüº¬Öظ´¶ÔÏóµÄ¼¯ºÏ.»»¾ä»°Ëµ,µ±ÄãдÁËÏÂÃæ´úÂë:

class Author {
   static hasMany = [books:Book]
}

GORM×¢ÈëµÄbooksÊôÐÔÊÇÒ»¸öjava.util.Set.ÕâÀïÓÐÒ»¸öÎÊÌ⣬µ±·ÃÎÊÕâ¸ö¼¯ºÏʱÊÇÎÞÐòµÄ,Õâ¿ÉÄܲ»ÊÇÄãÏëÒªµÄ.ΪÁË»ñµÃ×Ô¶¨ÒåµÄ˳ÐòÄã¿ÉÒÔ½«Õâ¸ö¼¯ºÏÉèÖÃΪSortedSet:

class Author {
   SortedSet books
   static hasMany = [books:Book]
}

ÔÚÕâÀïʹÓÃÁËjava.util.SortedSet ʵÏÖ,Õâ¾ÍÒâζ×ÅÄãµÄBookÀà±ØÐëʵÏÖ java.lang.Comparable ½Ó¿Ú:

class Book implements Comparable {
   String title
   Date releaseDate = new Date()

int compareTo(obj) { releaseDate.compareTo(obj.releaseDate) } }

ÉÏÃæÀàµÄ½á¹ûÊÇAuthorÀàµÄbooks¼¯ºÏÖÐËùÓеÄBookʵÀý½«°´ËûÃǵķ¢²¼ÈÕÆÚÅÅÐò.

¶ÔÏóÁбí

Èç¹ûÄãÖ»ÊÇÏëÄܹ»¼òµ¥µÄ±£³Ö¶ÔÏó°´ÕÕËûÃDZ»¼Ó½øÈ¥µÄ˳ÐòÅÅÐò£¬²¢ÄÜÏñÊý×éÒ»Ñù°´ÕÕË÷ÒýÀ´ÒýÓÃËûÃÇ,Äã¿ÉÒÔ¶¨ÒåÄãµÄ¼¯ºÏÀàÐÍΪList:

class Author {
   List books
   static hasMany = [books:Book]
}

ÔÚÕâÖÖÇé¿öϵ±ÄãÏòbooks¼¯ºÏÖÐÌí¼ÓÒ»¸öÐÂÔªËØÊ±,Õâ¸ö˳Ðò½«»á±£´æÔÚÒ»¸ö´Ó0¿ªÊ¼µÄÁбíË÷ÒýÖÐ,Òò´ËÄã¿ÉÒÔ:

author.books[0] // get the first book

ÕâÖÖ·½·¨ÔÚÊý¾Ý¿â²ãµÄ¹¤×÷Ô­ÀíÊÇ:ΪÁËÔÚÊý¾Ý¿â²ã±£´æÕâ¸ö˳Ðò,Hibernate´´½¨Ò»¸ö½Ð×öbooks_idxµÄÁÐ,Ëü±£´æ×ŸÃÔªËØÔÚ¼¯ºÏÖеÄË÷Òý.

µ±Ê¹Óà List ʱ,ÔªËØÔÚ±£´æÖ®Ç°±ØÐëÏÈÌí¼Óµ½¼¯ºÏÖÐ,·ñÔòHibernate»áÅ׳öÒì³£(org.hibernate.HibernateException: null index column for collection):

// This won't work!
def book = new Book(title: 'The Shining')
book.save()
author.addToBooks(book)

// Do it this way instead. def book = new Book(title: 'Misery') author.addToBooks(book) author.save()

Ó³Éä(Maps)¶ÔÏó

Èç¹ûÄãÏëÒªÒ»¸ö¼òµ¥µÄ string/value ¶Ômap,GROM¿ÉÒÔÓÃÏÂÃæ·½·¨À´Ó³Éä:

class Author {
   Map books // my of ISBN:book names
}

def a = new Author() a.books = ["1590597583":"Grails Book"] a.save()

ÕâÖÖÇé¿ömapµÄ¼üºÍÖµ¶¼±ØÐëÊÇ×Ö·û´®.

Èç¹ûÄãÏëÓÃÒ»¸ö¶ÔÏóµÄmap,ÄÇôÄã¿ÉÒÔÕâÑù×ö:

class Book {
  Map authors
  static hasMany = [authors:Author]
}

def a = new Author(name:"Stephen King")

def book = new Book() book.authors = [stephen:a] book.save()

static hasMany ÊôÐÔ¶¨ÒåÁËmapÖÐÔªËØµÄÀàÐÍ,mapÖеÄkey ±ØÐë ÊÇ×Ö·û´®

5.3 ³Ö¾Ã»¯»ù´¡

¹ØÓÚGrailsÒª¼ÇסµÄºÜÖØÒªµÄÒ»µã¾ÍÊÇ£¬GrailsµÄµ×²ãʹÓÃHibernate À´½øÐг־û¯.Èç¹ûÄúÒÔǰʹÓõÄÊÇActiveRecord »òÕß iBatis , Äú¿ÉÄÜ»á¶ÔHibernateµÄ"session"Ä£Ð͸е½ÓеãİÉú.

±¾ÖÊÉÏ,Grails×Ô¶¯°ó¶¨Hibernate sessionµ½µ±Ç°ÕýÔÚÖ´ÐеÄÇëÇóÉÏ.ÕâÔÊÐíÄãÏñʹÓÃGORMµÄÆäËû·½·¨Ò»ÑùºÜ×ÔÈ»µØÊ¹ÓÃsave ºÍ delete·½·¨ .

5.3.1 ±£´æºÍ¸üÐÂ

ÏÂÃæ¿´Ò»¸öʹÓà save ·½·¨µÄÀý×Ó:
def p = Person.get(1)
p.save()

HibernateµÄÒ»¸öÖ÷ÒªµÄ²»Í¬ÔÚÓÚµ±Äãµ÷Óà save ʱËü²»ÐèÒªÂíÉÏÖ´ÐÐÈκÎSQL²Ù×÷.Hibernateͨ³£½«SQLÓï¾ä·ÖÅú,×îºóÖ´ÐÐËûÃÇ.¶ÔÄãÀ´Ëµ,ÕâЩһ°ã¶¼ÊÇÓÉGrails×Ô¶¯Íê³ÉµÄ,Ëü¹ÜÀí×ÅÄãµÄHibernate session.

Ò²ÓÐÒ»Ð©ÌØÊâÇé¿ö,ÓÐʱºòÄã¿ÉÄÜÏë×Ô¼º¿ØÖÆÄÇЩÓï¾äʲôʱºò±»Ö´ÐÐ,»òÕßÓÃHibernateµÄÊõÓïÀ´Ëµ,¾ÍÊÇʲôʱºòsession±»"flushed".ÒªÕâÑùµÄ»°,Äã¿ÉÒÔ¶Ôsave·½·¨Ê¹ÓÃflush²ÎÊý:

def p = Person.get(1)
p.save(flush:true)

ÐèҪעÒâµÄÊÇ,Õâʱ°üÀ¨±£´æÖ®Ç°ËùÓеȴýÖ´ÐеÄSQLÓï¾ä¶¼»áͬ²½µ½Êý¾Ý¿âÖÐ.ÄãÒ²¿ÉÒÔ²¶»ñÈκÎÅ׳öµÄÒì³£,Õâͨ³£ÔÚ°üº¬ÁËÀÖ¹ÛËøµÄ¸ß²¢·¢Çé¿öÏ·dz£ÓÐÓÃ.

def p = Person.get(1)
try {
	p.save(flush:true)
}
catch(Exception e) {
	// deal with exception
}

5.3.2 ɾ³ý¶ÔÏó

ÏÂÃæÊÇdelete·½·¨µÄÒ»¸öÀý×Ó:
def p = Person.get(1)
p.delete()

delete ·½·¨Ò²ÔÊÐíͨ¹ý flush ²ÎÊýÀ´¿ØÖÆflushing.

def p = Person.get(1)
p.delete(flush:true)

×¢ÒâGrailsûÓÐÌṩ deleteAll ·½·¨,ÒòΪɾ³ýÊý¾ÝÊÇdiscouragedµÄ£¬¶øÇÒͨ³£¿ÉÒÔͨ¹ý²¼¶û±ê¼Ç/Âß¼­À´±ÜÃâ.

Èç¹ûÄãȷʵÐèÒªÅúÁ¿É¾³ýÊý¾Ý,Äã¿ÉÒÔʹÓÃexecuteUpdate·½·¨À´Ö´ÐÐÅúÁ¿µÄDMLÓï¾ä:

Customer.executeUpdate("delete Customer c where c.name = :oldName", [oldName:"Fred"])

5.3.3 ¼¶Áª¸üкÍɾ³ý

ÔÚʹÓÃGORMʱ,Àí½âÈçºÎ¼¶Áª¸üкÍɾ³ýÊǺÜÖØÒªµÄ.ÐèÒª¼ÇסµÄ¹Ø¼üÊÇ belongsTo µÄÉèÖÿØÖÆ×ÅÄĸöÀà"ÓµÓÐ"Õâ¸ö¹ØÁª.

ÎÞÂÛÊÇÒ»¶ÔÒ»,Ò»¶Ô¶à»¹ÊǶà¶Ô¶à,Èç¹ûÄ㶨ÒåÁËbelongsTo ,¸üкÍɾ³ý½«»á´ÓÓµÓÐÀൽ±»ËüÓµÓеÄÀà(¹ØÁªµÄÁíÒ»·½)¼¶Áª²Ù×÷.

Èç¹ûÄãûÓж¨Òå belongsTo ,ÄÇô¾ÍÄܼ¶Áª²Ù×÷,Ä㽫ÐèÒªÊÖ¹¤±£´æÃ¿¸ö¶ÔÏó.

ÏÂÃæÊÇÒ»¸öÀý×Ó:

class Airport {
	String name
	static hasMany = [flights:Flight]
}
class Flight {
	String number
	static belongsTo = [airport:Airport]
}

Èç¹ûÎÒÏÖÔÚ´´½¨Ò»¸öAirport ¶ÔÏó,²¢ÏòËüÌí¼ÓһЩ Flight,Ëü¿ÉÒÔ±£´æÕâ¸öAirport ²¢¼¶Áª±£´æÃ¿¸öflight,Òò´Ë»á±£´æÕû¸ö¶ÔÏóͼ:

new Airport(name:"Gatwick")
	 .addToFlights(new Flight(number:"BA3430"))
	 .addToFlights(new Flight(number:"EZ0938"))
	 .save()

Ïà·´µÄ,Èç¹ûÉÔºóÎÒɾ³ýÁËÕâ¸öAirport,ËùÓиúËü¹ØÁªµÄFlightÒ²¶¼½«»á±»É¾³ý:

def airport = Airport.findByName("Gatwick")
airport.delete()

È»¶ø,Èç¹ûÎÒ½« belongsTo È¥µôµÄ»°,ÉÏÃæµÄ¼¶ÁªÉ¾³ý´úÂë¾Í²»Äܹ¤×÷ÁË.

5.3.4 Á¢¼´¼ÓÔØºÍÑÓ³Ù¼ÓÔØ

ÔÚGORMÖÐ,¹ØÁªÄ¬ÈÏÊÇlazyµÄ.×îºÃµÄ½âÊÍÊÇÀý×Ó:

class Airport {
	String name
	static hasMany = [flights:Flight]
}
class Flight {
	String number
	static belongsTo = [airport:Airport]
}

ÉÏÃæµÄdomainÀàºÍÏÂÃæµÄ´úÂë:

def airport = Airport.findByName("Gatwick")
airport.flights.each {
	println it.name
}

GORM½«»áÖ´ÐÐÒ»¸öµ¥¶ÀµÄSQL²éѯÀ´×¥È¡Airport ʵÀý,È»ºóÔÙÓÃÒ»¸ö¶îÍâµÄ²éѯÖðÌõµü´ú flights ¹ØÁª.»»¾ä»°Ëµ,ÄãµÃµ½ÁËN+1Ìõ²éѯ.

¸ù¾ÝÕâ¸ö¼¯ºÏµÄʹÓÃÆµÂÊ,ÓÐʱºòÕâ¿ÉÄÜÊÇ×î¼Ñ·½°¸.ÒòΪÄã¿ÉÒÔÖ¸¶¨Ö»ÓÐÔÚÌØ¶¨µÄÇé¿öϲŷÃÎÊÕâ¸ö¹ØÁªµÄÂß¼­.

Ò»¸ö¿ÉÑ¡µÄ·½°¸ÊÇʹÓÃÁ¢¼´×¥È¡,Ëü¿ÉÒÔ°´ÕÕÏÂÃæµÄ·½·¨À´Ö¸¶¨:

class Airport {
	String name
	static hasMany = [flights:Flight]
	static fetchMode = [flights:"eager"]
}

ÔÚÕâÖÖÇé¿öÏÂ,AirportʵÀý¶ÔÓ¦µÄflights¹ØÁª»á±»Ò»´ÎÐÔÈ«²¿¼ÓÔØ½øÀ´(ÒÀÀµÓÚÓ³Éä).ÕâÑùµÄºÃ´¦ÊÇÖ´ÐиüÉٵIJéѯ,µ«ÊÇҪСÐÄʹÓÃ,ÒòΪʹÓÃÌ«¶àµÄeager¹ØÁª¿ÉÄܻᵼÖÂÄ㽫Õû¸öÊý¾Ý¿â¼ÓÔØ½øÄÚ´æ.

¹ØÁªÒ²¿ÉÒÔÓà ORM DSL ½«¹ØÁªÉùÃ÷Ϊ non-lazy

5.3.4 ±¯¹ÛËøºÍÀÖ¹ÛËø

ÀÖ¹ÛËø

ĬÈϵÄGORMÀà±»ÅäÖÃΪÀÖ¹ÛËø¡£ÀÖ¹ÛËøÊµÖÊÉÏÊÇHibernateµÄÒ»¸öÌØÐÔ£¬ËüÔÚÊý¾Ý¿âÀïÒ»¸öÌØ±ðµÄ version ×Ö¶ÎÖб£´æÁËÒ»¸ö°æ±¾ºÅ¡£

versionÁжÁÈ¡°üº¬µ±Ç°ÄãËù·ÃÎʵij־û¯ÊµÀýµÄ°æ±¾×´Ì¬µÄversionÊôÐÔ:

def airport = Airport.get(10)

println airport.version

µ±ÄãÖ´ÐиüвÙ×÷ʱ£¬Hibernate½«×Ô¶¯¼ì²éversionÊôÐÔºÍÊý¾Ý¿âÖÐversionÁУ¬Èç¹ûËûÃDz»Í¬£¬½«»áÅ׳öÒ»¸öStaleObjectExceptionÒì³££¬²¢ÇÒµ±Ç°ÊÂÎïÒ²»á±»»Ø¹ö¡£

ÕâÊǺÜÓÐÓõģ¬ÒòΪËüÔÊÐíÄ㲻ʹÓñ¯¹ÛËø(ÓÐһЩÐÔÄÜÉϵÄËðʧ)¾Í¿ÉÒÔ»ñµÃÒ»¶¨µÄÔ­×ÓÐÔ¡£ÓÉ´Ë´øÀ´µÄ¸ºÃæÓ°ÏìÊÇ£¬Èç¹ûÄãÓÐһЩ¸ß²¢·¢µÄд²Ù×÷µÄ»°£¬Äã±ØÐë´¦ÀíÕâ¸öÒì³£¡£ÕâÐèҪˢ³ö(flushing)µ±Ç°µÄsession£º

def airport = Airport.get(10)

try { airport.name = "Heathrow" airport.save(flush:true) } catch(org.springframework.dao.OptimisticLockingFailureException e) { // deal with exception }

Äã´¦ÀíÒì³£µÄ·½·¨È¡¾öÓÚÄãµÄÓ¦Óá£Äã¿ÉÒÔ³¢ÊԺϲ¢Êý¾Ý,»òÕß·µ»Ø¸øÓû§²¢ÈÃËûÃÇÀ´´¦Àí³åÍ».

×÷ΪѡÔñ£¬Èç¹ûËü³ÉÁËÎÊÌ⣬Äã¿ÉÒÔÇóÖúÓÚ±¯¹ÛËø¡£

±¯¹ÛËø¡£

±¯¹ÛËøµÈ¼ÛÓÚÖ´ÐÐÒ»¸ö SQL "SELECT * FOR UPDATE" Óï¾ä ²¢Ëø¶¨Êý¾Ý¿âÖеÄÒ»ÐС£ÕâÒâζ×ÅÆäËûµÄ¶Á²Ù×÷½«»á±»Ëø¶¨Ö±µ½Õâ¸öËø·Å¿ª¡£

ÔÚGrailsÖб¯¹ÛËøÍ¨¹ýlock ·½·¨Ö´ÐÐ:

def airport = Airport.get(10)
airport.lock() // lock for update
airport.name = "Heathrow"
airport.save()

Ò»µ©µ±Ç°ÊÂÎï±»Ìá½»£¬Grails»á×Ô¶¯µÄΪÄãÊÍ·ÅËø¡£

5.4 GORM²éѯ

GORMÌṩÁË´Ó¶¯Ì¬²éѯÆ÷µ½criteriaµ½HibernateÃæÏò¶ÔÏó²éѯÓïÑÔHQLµÄһϵÁвéѯ·½Ê½¡£

Groovyͨ¹ýGPath ²Ù×ݼ¯ºÏµÄÄÜÁ¦£¬ºÍGORMµÄÏñsort,findAllµÈ·½·¨½áºÏÆðÀ´£¬ÐγÉÁËÒ»¸öÇ¿´óµÄ×éºÏ¡£

µ«ÊÇ£¬ÈÃÎÒÃÇ´Ó»ù´¡¿ªÊ¼°É¡£

»ñȡʵÀýÁбí

Èç¹ûÄã¼òµ¥µÄÐèÒª»ñµÃ¸ø¶¨ÀàµÄËùÓÐʵÀý£¬Äã¿ÉÒÔʹÓÃlist·½·¨:

def books = Book.list()

list ·½·¨Ö§³Ö·ÖÒ³²ÎÊý:

def books = Book.list(offset:10, max:20)

Ò²¿ÉÒÔÅÅÐò:

def books = Book.list(sort:"asc", order:"title")

¸ù¾ÝÊý¾Ý¿â±êʶ·ûÈ¡»Ø

µÚ¶þ¸öÈ¡»ØµÄ»ù±¾ÐÎʽÊǸù¾ÝÊý¾Ý¿â±êʶ·ûÈ¡»Ø£¬Ê¹ÓÃget ·½·¨:

def book = Book.get(23)

ÄãÒ²¿ÉÒÔ¸ù¾ÝÒ»¸ö±êʶ·ûµÄ¼¯ºÏʹÓÃgetAll·½·¨È¡µÃÒ»¸öʵÀýÁбí:

def books = Book.getAll(23, 93, 81)

5.4.1 ¶¯Ì¬²éÕÒÆ÷

GORMÖ§³Ö¶¯Ì¬²éÕÒÆ÷µÄ¸ÅÄî¡£¶¯Ì¬²éÕÒÆ÷¿´ÆðÀ´ÏñÒ»¸ö¾²Ì¬·½·¨µÄµ÷Ó㬵«ÊÇÕâЩ·½·¨±¾ÉíÔÚ´úÂëÖÐʵ¼ÊÉϲ¢²»´æÔÚ¡£

¶øÊÇÔÚÔËÐÐʱ»ùÓÚÒ»¸ö¸ø¶¨ÀàµÄÊôÐÔ,×Ô¶¯Éú³ÉÒ»¸ö·½·¨¡£±ÈÈçÀý×ÓÖеÄBookÀà:

class Book {
	String title
	Date releaseDate
	Author author
}                
class Author {
	String name
}

Book ÀàÓÐһЩÊôÐÔ£¬±ÈÈçtitle, releaseDate ºÍ author. ÕâЩ¶¼¿ÉÒÔ°´ÕÕ·½·¨±í´ïʽµÄ¸ñʽ±»ÓÃÓÚfindBy ºÍ findAllBy ·½·¨¡£

def book = Book.findByTitle("The Stand")

book = Book .findByTitleLike("Harry Pot%")

book = Book .findByReleaseDateBetween( firstDate, secondDate )

book = Book .findByReleaseDateGreaterThan( someDate )

book = Book .findByTitleLikeOrReleaseDateLessThan( "%Something%", someDate )

·½·¨±í´ïʽ

ÔÚGORMÖÐÒ»¸ö·½·¨±í´ïʽÓÉǰ׺(±ÈÈçfindBy)ºóÃæ¸úÒ»¸ö±í´ïʽ×é³É£¬Õâ¸ö±í´ïʽÓÉÒ»¸ö»ò¶à¸öÊôÐÔ×é³É¡£»ù±¾ÐÎʽÊÇ:

Book.findBy[Property][Suffix]*[Boolean Operator]*[Property][Suffix]

ÓÃ*±ê¼ÇµÄ²¿·ÖÊÇ¿ÉÑ¡µÄ¡£Ã¿¸öºó׺¶¼»á¸Ä±ä²éѯµÄÐÔÖÊ¡£ÀýÈç:

def book = Book.findByTitle("The Stand")

book = Book.findByTitleLike("Harry Pot%")

ÔÚÉÏÃæµÄÀý×ÓÖУ¬µÚÒ»¸ö²éѯµÈ¼ÛÓÚµÈÓÚºóÃæµÄÖµ¡£µÚ¶þ¸öÒòΪÔö¼ÓÁË Likeºó׺£¬ËüµÈ¼ÛÓÚSQLµÄlike±í´ïʽ¡£

¿ÉÓõĺó׺°üÀ¨:

Äã»á·¢ÏÖ×îºóÈý¸ö·½·¨±ê×¢Á˲ÎÊýµÄ¸öÊý£¬ËûÃǵÄʾÀýÈçÏ£º

def now = new Date()
def lastWeek = now - 7
def book =
  Book
    .findByReleaseDateBetween( lastWeek, now )

ͬÑùµÄisNull ºÍ isNotNull ²»ÐèÒª²ÎÊý:

def books = Book.findAllByReleaseDateIsNull()

²¼¶ûÂß¼­(AND/OR)

·½·¨±í´ïʽҲ¿ÉÒÔʹÓÃÒ»¸ö²¼¶û²Ù×÷·ûÀ´×éºÏÁ½¸öcriteria£º

def books = 
	 Book
	   .findAllByTitleLikeAndReleaseDateGreaterThan("%Java%", new Date()-30)

ÔÚÕâÀïÎÒÃÇÔÚ²éѯÖмäʹÓà AndÀ´È·±£Á½¸öÌõ¼þ¶¼Âú×㣬µ«ÊÇͬÑùµØÄãÒ²¿ÉÒÔʹÓà Or:

def books = 
	 Book
	   .findAllByTitleLikeOrReleaseDateGreaterThan("%Java%", new Date()-30)

ÏÔÈ»ÕâÖÖÇé¿öÏ·½·¨Ãû»á±äµÃÏ൱³¤£¬ÕâʱºòÄãÓ¦¸Ã¿¼ÂÇʹÓà Ìõ¼þ²éѯ.

²éѯ¹ØÁª

¹ØÁªÒ²¿ÉÒÔ±»ÓÃÔÚ²éѯÖÐ:

def author = Author.findByName("Stephen King")

def books = author ? Book.findAllByAuthor(author) : []

ÔÚÕâÀïÈç¹û Author ʵÀý²»Îªnull,ÎÒÃÇÔÚ²éѯÖÐÓÃËüÈ¡µÃ¸ø¶¨AuthorµÄËùÓÐ Book ʵÀý.

·ÖÒ³ºÍÅÅÐò

¸úlist ·½·¨ÉÏ¿ÉÓõķÖÒ³ºÍÅÅÐò²ÎÊýÒ»Ñù£¬ËûÃÇͬÑù¿ÉÒÔ±»ÌṩΪһ¸ömapÓÃÓÚ¶¯Ì¬²éѯÆ÷µÄ×îºóÒ»¸ö²ÎÊý¡£

def books = 
  Book.findAllByTitleLike("Harry Pot%", [max:3, 
                                         offset:2, 
                                         sort:"asc", 
                                         order:"title"])

5.4.2 Ìõ¼þ²éѯ

CriteriaÊÇÒ»ÖÖÀàÐͰ²È«µÄ¡¢¸ß¼¶µÄ²éѯ·½·¨£¬ËüʹÓÃGroovy builder¹¹ÔìÇ¿´ó¸´ÔӵIJéѯ¡£ËüÊÇÒ»ÖÖ±ÈʹÓÃStringBufferºÃµÃ¶àµÄÑ¡Ôñ¡£

Criteria¿ÉÒÔͨ¹ýcreateCriteria »òÕß withCriteria ·½·¨À´Ê¹Óá£builderʹÓÃHibernateµÄCriteria API,builderÉϵĽڵã¶ÔÓ¦Hibernate Criteria APIÖÐ Restrictions ÀàÖеľ²Ì¬·½·¨¡£Ó÷¨Ê¾Àý:

def c = Account.createCriteria()
def results = c {
	like("holderFirstName", "Fred%")
	and {
		between("balance", 500, 1000)
		eq("branch", "London")
	}
	maxResults(10)
	order("holderLastName", "desc")
}

Âß¼­Ó루Conjunctions£©ºÍÂß¼­»ò£¨Disjunctions£©

ÈçÇ°ÃæÀý×ÓËùÑÝʾµÄ£¬Äã¿ÉÒÔÓÃand { } ¿éÀ´·Ö×écriteriaµ½Ò»¸öÂß¼­AND:

and {
	between("balance", 500, 1000)
	eq("branch", "London")
}

Âß¼­ORÒ²¿ÉÒÔÕâô×ö:

or {
	between("balance", 500, 1000)
	eq("branch", "London")
}

ÄãÒ²¿ÉÒÔÓÃÂß¼­NOTÀ´·ñ¶¨:

not {
	between("balance", 500, 1000)
	eq("branch", "London")
}

²éѯ¹ØÁª

¹ØÁª¿ÉÒÔͨ¹ýʹÓÃÒ»¸ö¸ú¹ØÁªÊôÐÔͬÃûµÄ½ÚµãÀ´²éѯ¡£±ÈÈçÎÒÃÇ˵AccountÀàÓйØÁªµ½¶à¸ö Transaction ¶ÔÏó:

class Account {
    …
    def hasMany = [transactions:Transaction]
    Set transactions
    …
}

ÎÒÃÇ¿ÉÒÔʹÓÃÊôÐÔÃû transaction ×÷ΪbuilderµÄÒ»¸ö½ÚµãÀ´²éѯÕâ¸ö¹ØÁª:

def c = Account.createCriteria()
def now = new Date()
def results = c.list {
       transactions {
            between('date',now-10, now)
       }
}

The above code will find all the Account instances that have performed transactions within the last 10 days. ÉÏÃæµÄ´úÂ뽫»á²éÕÒËùÓйýÈ¥10ÌìÄÚÖ´Ðйýtransactions µÄ AccountʵÀý¡£ÄãÒ²¿ÉÒÔÔÚÂß¼­¿éÖÐǶÌ×¹ØÁª²éѯ:

def c = Account.createCriteria()
def now = new Date()
def results = c.list {
     or {
        between('created',now-10,now)
        transactions {
             between('date',now-10, now)
        }
     }
}

ÕâÀï,ÎÒÃǽ«ÕÒ³öÔÚ×î½ü10ÌìÄÚ½øÐйý½»Ò×»òÕß×î½ü10ÌìÄÚд´½¨µÄËùÓÐÓû§¡£

ͶӰ(Projections)²éѯ

ͶӰ±»ÓÃÓÚ¶¨ÖƲéѯ½á¹û¡£ÒªÊ¹ÓÃͶӰÄãÐèÒªÔÚcriteria builderÊ÷ÀﶨÒåÒ»¸ö"projections"½Úµã¡£projections½ÚµãÄÚ¿ÉÓõķ½·¨µÈͬÓÚ Hibernate µÄProjections ÀàÖеķ½·¨.

def c = Account.createCriteria()

def numberOfBranches = c.get { projections { countDistinct('branch') } }

ʹÓÿɹö¶¯µÄ½á¹û

Äã¿ÉÒÔͨ¹ýµ÷ÓÃscroll·½·¨À´Ê¹ÓÃHibernateµÄScrollableResults ÌØÐÔ¡£

def results = crit.scroll {
      maxResults(10)
}
def f = results.first()
def l = results.last()
def n = results.next()
def p = results.previous()

def future = results.scroll(10) def accountNumber = results.getLong('number')

ÏÂÃæÒýÓõÄÊÇHibernateÎĵµÖйØÓÚScrollableResultsµÄÃèÊö:

½á¹û¼¯µÄµü´úÆ÷£¨iterator£©¿ÉÒÔÒÔÈÎÒâ²½½øµÄ·½Ê½Ç°ºóÒÆ¶¯£¬¶øQuery / ScrollableResultsģʽ¸úJDBCµÄPreparedStatement/ ResultSetÒ²ºÜÏñ£¬Æä½Ó¿Ú·½·¨ÃûµÄÓïÒâÒ²¸úResultSetµÄÀàËÆ¡£

²»Í¬ÓÚJDBC£¬½á¹ûÁеıàºÅÊÇ´Ó0¿ªÊ¼.

ÔÚCriteriaʵÀýÖÐÉèÖÃÊôÐÔ

Èç¹ûÔÚbuilderÊ÷ÄÚ²¿µÄÒ»¸ö½Úµã²»Æ¥ÅäÈκÎÒ»ÏîÌØ¶¨±ê×¼£¬Ëü½«³¢ÊÔÉèÖÃΪCriteria¶ÔÏó×ÔÉíµÄÊôÐÔ¡£Òò´ËÔÊÐíÍêÈ«·ÃÎÊÕâ¸öÀàµÄËùÓÐÊôÐÔ¡£ÏÂÃæµÄÀý×ÓÊÇÔÚCriteriaʵÀýÉϵ÷ÓÃsetMaxResults ºÍ setFirstResult:

import org.hibernate.FetchMode as FM
	....
	def results = c.list {
		maxResults(10)
		firstResult(50)
		fetchMode("aRelationship", FM.EAGER)
	}

Á¢¼´×¥È¡µÄ·½Ê½²éѯ

ÔÚ Á¢¼´¼ÓÔØºÍÑÓ³Ù¼ÓÔØ Õâ½Ú£¬ÎÒÃÇÌÖÂÛÁËÈç¹ûÖ¸¶¨Ìض¨µÄץȡ·½Ê½À´±ÜÃâN+1²éѯµÄÎÊÌâ¡£Õâ¸öcriteria²éѯҲ¿ÉÒÔ×öµ½:

import org.hibernate.FetchMode as FM
// ......

def criteria = Task.createCriteria() def tasks = criteria.list{ eq("assignee.id", task.assignee.id) fetchMode('assignee', FM.EAGER) fetchMode('project', FM.EAGER) order('priority', 'asc') }

·½·¨ÒýÓÃ

Èç¹ûÄãµ÷ÓÃÒ»¸öûÓз½·¨ÃûµÄbuilder£¬±ÈÈç:

c { … }

ĬÈϵĻáÁгöËùÓнá¹û£¬Òò´ËÉÏÃæ´úÂëµÈ¼ÛÓÚ:

c.list { … }

·½·¨ÃèÊö
listÕâÊÇĬÈϵķ½·¨¡£Ëü»á·µ»ØËùÓÐÆ¥ÅäµÄÐС£
get·µ»ØÎ¨Ò»µÄ½á¹û¼¯£¬±ÈÈ磬¾ÍÒ»ÐС£criteriaÒѾ­¹æ¶¨ºÃÁË£¬½ö½ö²éѯһÐС£Õâ¸ö·½·¨¸ü·½±ã£¬ÃâµÃʹÓÃÒ»¸ölimitÀ´Ö»È¡µÚÒ»ÐÐʹÈËÃÔ»ó¡£
scroll·µ»ØÒ»¸ö¿É¹ö¶¯µÄ½á¹û¼¯
listDistinctÈç¹û×Ó²éѯ»òÕß¹ØÁª±»Ê¹Óã¬ÓÐÒ»¸ö¿ÉÄܾÍÊÇÔÚ½á¹û¼¯Öжà´Î³öÏÖͬһÐУ¬Õâ¸ö·½·¨ÔÊÐíÖ»Áгö²»Í¬µÄÌõÄ¿£¬ËüµÈ¼ÛÓÚCriteriaSpecification ÀàµÄDISTINCT_ROOT_ENTITY¡£

5.4.3 Hibernate²éѯÓïÑÔ

GORMÒ²Ö§³ÖHibernateµÄ²éѯÓïÑÔHQL,ÔÚHibernateÎĵµÖÐµÄ Chapter 14. HQL: The Hibernate Query Language ,¿ÉÒÔÕÒµ½Ëü·Ç³£ÍêÕûµÄ²Î¿¼Êֲᡣ

GORMÌṩÁËһЩʹÓÃHQLµÄ·½·¨£¬°üÀ¨find, findAll ºÍ executeQuery¡£ÏÂÃæÊÇÒ»¸ö²éѯµÄÀý×Ó:

def results =
      Book.findAll("from Book as b where b.title like 'Lord of the%'")

λÖúÍÃüÃû²ÎÊý

ÉÏÃæµÄÀý×ÓÖд«µÝ¸ø²éѯµÄÖµÊÇÓ²±àÂëµÄ£¬µ«ÊÇ£¬Äã¿ÉÒÔͬÑùµØÊ¹ÓÃλÖòÎÊý:

def results =
      Book.findAll("from Book as b where b.title like ?", ["The Shi%"])

»òÕßÉõÖÁʹÓÃÃüÃû²ÎÊý:

def results =
      Book.findAll("from Book as b where b.title like :search or b.author like :search", [search:"The Shi%"])

¶àÐвéѯ

Èç¹ûÄãÐèÒª½«²éѯ·Ö¸îµ½¶àÐÐÄã¿ÉÒÔʹÓÃÒ»¸öÐÐÁ¬½Ó·û:

def results = Book.findAll("""\\
from Book as b, \\
     Author as a \\
where b.author = a and a.surname = ?""", ['Smith'])

Groovy µÄ¶àÐÐ×Ö·û´®¶ÔHQL²éѯÎÞЧ

·ÖÒ³ºÍÅÅÐò

ʹÓÃHQL²éѯµÄʱºòÄãÒ²¿ÉÒÔ½øÐзÖÒ³ºÍÅÅÐò¡£Òª×öµÄÖ»ÊǼòµ¥Ö¸¶¨·ÖÒ³ºÍÅÅÐò²ÎÊý×÷Ϊһ¸öÉ¢ÁÐÔÚ·½·¨µÄĩβµ÷ÓÃ:

def results =
      Book.findAll("from Book as b where b.title like 'Lord of the%'", 
                   [max:10, offset:20, sort:"asc", order:"title"])

5.5 ¸ß¼¶GORMÌØÐÔ

½ÓÏÂÀ´µÄÕ½ڸ²¸Ç¸ü¶à¸ß¼¶µÄGORMʹÓà °üÀ¨ »º´æ¡¢¶¨ÖÆÓ³ÉäºÍʼþ

5.5.1 ʼþºÍ×Ô¶¯ÊµÏÖʱ¼ä´Á

GORM supports the registration of events as closures that get fired when certain events occurs such as deletes, inserts and updates. To add an event simply register the relevant closure with your domain class. GORMÖ§³Öʼþ×¢²á£¬Ö»ÐèÒª½«Ê¼þ×÷Ϊһ¸ö±Õ°ü¼´¿É£¬µ±Ä³¸öʼþ´¥·¢£¬±ÈÈçɾ³ý£¬²åÈ룬¸üС£ÎªÁËÌí¼ÓÒ»¸öʼþÐèÒªÔÚÄãµÄÁìÓòÀàÖÐÌí¼ÓÏà¹ØµÄ±Õ°ü¡£

ʼþÀàÐÍ

beforeInsertʼþ

´¥·¢µ±Ò»¸ö¶ÔÏó±£´æµ½Êý¾Ý¿â֮ǰ

class Person {
   Date dateCreated

def beforeInsert = { dateCreated = new Date() } }

beforeUpdateʼþ

¸üÐÂǰʼþ

class Person {
   Date dateCreated
   Date lastUpdated

def beforeInsert = { dateCreated = new Date() } def beforeUpdate = { lastUpdated = new Date() } }

beforeDeleteʼþ

´¥·¢µ«Ò»¸ö¶ÔÏó±»É¾³ý

class Person {
   String name
   Date dateCreated
   Date lastUpdated

def beforeDelete = { new ActivityTrace(eventName:"Person Deleted",data:name).save() } }

onLoadʼþ

´¥·¢µ±Ò»¸ö¶ÔÏó´ÓÊý¾Ý¿âÈ¡³ö

class Person {
   String name
   Date dateCreated
   Date lastUpdated

def onLoad = { name = "I'm loaded" } }

×Ô¶¯Ê±¼ä´Á

ÉÏÃæµÄÀý×ÓÑÝʾÁËʹÓÃʼþÀ´¸üÐÂÒ»¸ölastUpdatedºÍdateCreatedÊôÐÔÀ´¸ú×Ù¶ÔÏóµÄ¸üС£ÊÂʵÉÏ£¬ÕâЩÉèÖò»ÊDZØÐëµÄ¡£Í¨¹ý¼òµ¥µÄ¶¨ÒåÒ»¸ölastUpdatedºÍdateCreatedÊôÐÔ £¬GORM»á×Ô¶¯µÄΪÄã¸üС£

Èç¹û£¬ÕâЩÐÐΪ²»ÊÇÄãÐèÒªµÄ£¬¿ÉÒÔÆÁ±ÎÕâЩ¹¦ÄÜ¡£ÈçÏÂÉèÖÃ

class Person {
   Date dateCreated
   Date lastUpdated
   static mapping = {
      autoTimestamp false
   }
}

5.5.2 ×Ô¶¨ÒåORMÓ³Éä

Grails µÄÓò¶ÔÏó¿ÉÒÔÓ³Éäµ½Ðí¶àÒÅÁôµÄÄ£ÐÍͨ¹ý ¹ØÏµ¶ÔÏóÓ³ÉäÓòÓïÑÔ¡£½ÓÏÂÀ´µÄ²¿·Ö½«´øÄãÁìÂÔËüÊÇ¿ÉÄܵÄͨ¹ýORM DSL

ÕâÊDZØÒªµÄ£¬Èç¹ûÄã¸ßÐ˵ؼá³ÖÒÔÔ¼¶¨À´¶¨ÒåGORM¶ÔÓ¦µÄ±í£¬ÁÐÃûµÈ¡£ÄãÖ»ÐèÒªÕâ¸ö¹¦ÄÜ£¬Èç¹ûÄãÐèÒª¶¨ÖÆGORM Ó³Éäµ½ÒÅÁôÄ£ÐÍ»ò½øÐлº´æ

×Ô¶¨ÒåÓ³ÉäÊÇʹÓþ²Ì¬µÄmapping¿é¶¨ÒåÔÚÄãµÄÓòÀàÖеģº

class Person {
  ..
  static mapping = {

} }

5.5.2.1 ±íÃûºÍÁÐÃû

±íÃû

ÀàÓ³Éäµ½Êý¾Ý¿âµÄ±íÃû¿ÉÒÔͨ¹ýʹÓÃtable¹Ø¼ü×ÖÀ´¶¨ÖÆ

class Person {
  ..
  static mapping = {
      table 'people'
  }
}

ÔÚÉÏÃæµÄÀý×ÓÖУ¬Àà»áÓ³Éäµ½people±íÀ´´úÌæÄ¬ÈϵÄperson±í

ÁÐÃû

ͬÑù£¬Ò²ÊÇ¿ÉÄ͍ܵ֯ij¸öÁе½Êý¾Ý¿â¡£±ÈÈç˵£¬ÄãÏë¸Ä±äÁÐÃûÀý×ÓÈçÏÂ

class Person {
  String firstName
  static mapping = {
      table 'people'
      firstName column:'First_Name'
  }
}

ÔÚÕâ¸öÀý×ÓÖУ¬Ä㶨ÒåÁËÒ»¸öcolumn¿é£¬´Ë¿é°üº¬µÄ·½·¨µ÷ÓÃÆ¥Åäÿһ¸öÊôÐÔÃû³Æ£¨±¾Àý×ÓÖÐÊÇ firstName£©£¬½ÓÏÂÀ´Ê¹ÓÃÃüÃûµÄcolumnÀ´Ö¸¶¨×Ö¶ÎÃû³ÆµÄÓ³Éä

ÁÐÀàÐÍ

GORM supports configuration of Hibernate types via the DSL using the type attribute. This includes specifing user types that subclass the Hibernate org.hibernate.types.UserType class. As an example GORM»¹¿ÉÒÔͨ¹ýDSLµÄtypeÊôÐÔÀ´Ö§³ÖHibernateÀàÐÍ£¬°üÀ¨Ìض¨HibernateµÄorg.hibernate.types.UserTypeµÄ×ÓÀà¡£±ÈÈ磬ÓÐÒ»¸öPostCodeType,Äã¿ÉÒÔÏóÏÂÃæÕâÑùʹÓãº

class Address {
   String number
   String postCode
   static mapping = {
      postCode type:PostCodeType
   }
}

ÁíÍâÈç¹ûÄãÏ뽫ËüÓ³Éäµ½HibernateµÄ»ù±¾ÀàÐͶø²»ÊÇGrailsµÄĬÈÏÀàÐÍ£¬¿ÉÒԲο¼ÏÂÃæ´úÂ룺

class Address {
   String number
   String postCode
   static mapping = {
      postCode type:'text'
   }
}

ÉÏÃæµÄÀý×Ó½«Ê¹postCodeÁÐÓ³Éäµ½Êý¾Ý¿âµÄSQL TEXT»òÕßCLOBÀàÐÍ

Ò»¶ÔÒ»Ó³Éä

ÔÚ¹ØÁªÖУ¬ÄãÒ²Óлú»á¸Ä±äÍâ¼üÓ³ÉäÁªÏµ£¬ÔÚÒ»¶ÔÒ»µÄ¹ØÏµÖУ¬¶ÔÁеIJÙ×÷¸úÆäËû³£¹æµÄÁвÙ×÷²¢ÎÞ¶þÒ죬Àý×ÓÈçÏÂ

class Person {
  String firstName
  Address address
  static mapping = {
      table 'people'
      firstName column:'First_Name'
      address column:'Person_Adress_Id'
  }
}

ĬÈÏÇé¿öÏ£¬address½«Ó³Éäµ½Ò»¸öÃû³ÆÎªaddress_id µÄÍâ¼ü¡£µ«ÊÇʹÓÃÉÏÃæµÄÓ³É䣬ÎÒÃǸıäÍâ¼üÁÐΪPerson_Adress_Id

Ò»¶Ô¶àÓ³Éä

ÔÚÒ»¸öË«ÏòµÄÒ»¶Ô¶à¹ØÏµÖУ¬Äã¿ÉÒÔÏóǰ½ÚÖеÄÒ»¶ÔÒ»¹ØÏµÖÐÄÇÑù¸Ä±äÍâ¼üÁУ¬Ö»ÐèÒªÔÚ¶àµÄÒ»¶ËÖиıäÁÐÃû¼´¿É¡£È»¶ø£¬ÔÚµ¥Ïò¹ØÁªÖУ¬Íâ¼üÐèÒªÔÚ¹ØÁª×ÔÉíÖУ¨¼´Ò»µÄÒ»¶Ë-ÒëÕß×¢£©Ö¸¶¨¡£±ÈÈ磬¸ø¶¨Ò»¸öµ¥ÏòÒ»¶Ô¶àÁªÏµPersonºÍAddress£¬ÏÂÃæµÄ´úÂë»á¸Ä±äaddress±íÖÐÍâ¼ü¡£

class Person {
  String firstName
  static hasMany = [addresses:Address]
  static mapping = {
      table 'people'
      firstName column:'First_Name'
	  addresses column:'Person_Address_Id'
  }
}

Èç¹ûÄã²»ÏëÔÚaddress±íÖÐÓÐÕâ¸öÁУ¬¿ÉÒÔͨ¹ýÖÐ¼ä¹ØÁª±íÀ´Íê³É£¬Ö»ÐèҪʹÓÃjoinTable²ÎÊý¼´¿É£º

class Person {
  String firstName
  static hasMany = [addresses:Address]
  static mapping = {
      table 'people'
      firstName column:'First_Name'
      addresses joinTable:[name:'Person_Addresses', key:'Person_Id', column:'Address_Id']
  }
}

¶à¶Ô¶àÓ³Éä

ĬÈÏÇé¿öÏ£¬GrailsÖжà¶Ô¶àµÄÓ³ÉäÊÇͨ¹ýÖмä±íÀ´Íê³ÉµÄ£¬ÒÔÏÂÃæµÄ¶à¶Ô¶à¹ØÁªÎªÀý£º

class Group {
	…
	static hasMany = [people:Person]
}
class Person {
	…
	static belongsTo = Group
	static hasMany = [groups:Group]
}

ÔÚÉÏÃæµÄÀý×ÓÖУ¬Grails½«»á´´½¨Ò»¸ögroup_person±í°üº¬Íâ¼üperson_idºÍgroup_id¶ÔÓ¦personºÍgroup±í¡£¼ÙÈçÄãÐèÒª¸Ä±äÁÐÃû£¬Äã¿ÉÒÔΪÿ¸öÀàÖ¸¶¨Ò»¸öÁÐÓ³Éä

class Group {
   …
   static mapping = {
       people column:'Group_Person_Id'
   }	
}
class Person {
   …
   static mapping = {
       groups column:'Group_Group_Id'
   }	
}

ÄãÒ²¿ÉÒÔÖ¸¶¨Öмä±íµÄÃû³Æ

class Group {
   …
   static mapping = {
       people column:'Group_Person_Id',joinTable:'PERSON_GROUP_ASSOCIATIONS'
   }	
}
class Person {
   …
   static mapping = {
       groups column:'Group_Group_Id',joinTable:'PERSON_GROUP_ASSOCIATIONS'
   }	
}

5.5.2.2 »º´æ²ßÂÔ

ÉèÖûº´æ

Hibernate ±¾ÉíÌṩÁË×Ô¶¨Òå¶þ¼¶»º´æµÄÌØÐÔ£¬Õâ¾ÍÐèÒªÔÚgrails-app/conf/DataSource.groovyÎļþÖÐÅäÖãº

hibernate {
    cache.use_second_level_cache=true
    cache.use_query_cache=true
    cache.provider_class='org.hibernate.cache.EhCacheProvider'
}

µ±È»£¬ÄãÒ²¿ÉÒÔ°´ÄãËùÐèÀ´¶¨ÖÆÉèÖᣱÈÈ磬ÄãÏëʹÓ÷ֲ¼Ê½»º´æ»úÖÆ

ÏëÁ˽â¸ü¶àHibernateµÄ¶þ¼¶»º´æ£¬²Î¿¼HibernateÏà¹ØÎĵµ

»º´æÊµÀý

¼ÙÈçÒªÔÚÓ³Éä´úÂë¿éÖÐÆôÓÃȱʡµÄ»º´æ£¬¿ÉÒÔͨ¹ýµ÷ÓÃcache·½·¨ÊµÏÖ£º

class Person {
  ..
  static mapping = {
      table 'people'
      cache true
  }
}

ÉÏÃæµÄÀý×ÓÖн«ÅäÖÃÒ»¸ö¶Á-д(read-write)»º´æ°üÀ¨lazyºÍnon-lazyÊôÐÔ¡£¼ÙÈçÄãÏë¶¨ÖÆÕâÐ©ÌØÐÔ£¬Äã¿ÉÒÔÈçÏÂËùʾ£º

class Person {
  ..
  static mapping = {
      table 'people'
      cache usage:'read-only', include:'non-lazy'
  }
}

»º´æ¹ØÁª¶ÔÏó

¾ÍÏñʹÓÃHibernateµÄ¶þ¼¶»º´æÀ´»º´æÊµÀýÒ»Ñù£¬ÄãÒ²¿ÉÒÔÀ´»º´æ¼¯ºÏ£¨¹ØÁª£©£¬±ÈÈ磺

class Person {
  String firstName
  static hasMany = [addresses:Address]
  static mapping = {
      table 'people'
      version false
      addresses column:'Address', cache:true
  }
}
class Address {
   String number
   String postCode
}

ÉÏÃæµÄÀý×ÓÖУ¬ÎÒÃÇÔÚaddresses¼¯ºÏÆôÓÃÁËÒ»¸ö¶Á-д»º´æ£¬ÄãÒ²¿ÉÒÔʹÓÃ

cache:'read-write' // or 'read-only' or 'transactional'

¸ü¶àÅäÖÃÇë²Î¿¼»º´æÓ÷¨

»º´æÓ÷¨

ÏÂÃæÊDz»Í¬»º´æÉèÖúÍËûÃǵÄʹÓ÷½·¨

5.5.2.3 ¼Ì³Ð²ßÂÔ

ĬÈÏÇé¿öÏÂGORM ÀàʹÓÃtable-per-hierarchyÀ´Ó³Éä¼Ì³ÐµÄ¡£Õâ¾ÍÓÐÒ»¸öȱµã¾ÍÊÇÔÚÊý¾Ý¿â²ãÃæ£¬Áв»ÄÜÓÐNOT-NULLµÄÔ¼Êø¡£Èç¹ûÄã¸üϲ»¶table-per-subclass£¬Äã¿ÉÒÔʹÓÃÏÂÃæ·½·¨

class Payment {
    Long id
    Long version
    Integer amount

static mapping = { tablePerHierarchy false } } class CreditCardPayment extends Payment { String cardNumber }

ÔÚ׿ÏÈPaymentÀàµÄÓ³ÉäÉèÖÃÖУ¬Ö¸¶¨ÁËÔÚËùÓеÄ×ÓÀàÖУ¬²»Ê¹ÓÃtable-per-hierarchyÓ³Éä¡£

5.5.2.4 ×Ô¶¨ÒåÊý¾Ý¿â±êʶ·û

Äã¿ÉÒÔͨ¹ýDSLÀ´¶¨ÖÆGORMÉú³ÉÊý¾Ý¿â±êʶ£¬È±Ê¡Çé¿öÏÂGORM½«¸ù¾ÝÔ­ÉúÊý¾Ý¿â»úÖÆÀ´Éú³Éids£¬ÕâÊÇÆù½ñΪֹ×îºÃµÄ·½·¨£¬µ«ÊÇÈÔ´æÔÚÐí¶àģʽ£¬²»Í¬µÄ·½·¨À´Éú³É±êʶ¡£

Ϊ´Ë£¬HibernateÌØµØ¶¨ÒåÁËidÉú³ÉÆ÷µÄ¸ÅÄÄã¿ÉÒÔ×Ô¶¨ÒåËüÒªÓ³ÉäµÄidÉú³ÉÆ÷ºÍÁУ¬ÈçÏ£º

class Person {
  ..
  static mapping = {
      table 'people'
      version false
      id generator:'hilo', params:[table:'hi_value',column:'next_value',max_lo:100]
  }
}

ÔÚÉÏÃæµÄÀý×ÓÖУ¬ÎÒÃÇʹÓÃÁËHibernateÄÚÖõÄ'hilo'Éú³ÉÆ÷£¬´ËÉú³ÉÆ÷ͨ¹ýÒ»¸ö¶ÀÁ¢µÄ±íÀ´Éú³Éids¡£´ËÍ⻹ÓÐÐí¶à²»Í¬µÄÉú³ÉÆ÷¿ÉÒÔÅäÖ㬾ßÌå²Î¿¼HibernateÔÚÕâ¸öÖ÷ÌâÉϵÄÏà¹ØÎĵµ¡£

ÏëÁ˽â¸ü¶à²»Í¬µÄHibernateÉú³ÉÆ÷Çë²Î¿¼HibernateÎĵµ

×¢Ò⣬Èç¹ûÄã½ö½öÏë¶¨ÖÆÁÐid£¬Äã¿ÉÒÔÕâÑù

class Person {
  ..
  static mapping = {
      table 'people'
      version false
      id column:'person_id'
  }
}

5.5.2.5 ¸´ºÏÖ÷¼ü

GORMÖ§³Ö¸´ºÏ±êʶ£¨¸´ºÏÖ÷¼ü--ÒëÕß×¢£©¸ÅÄ±êʶÓÉÁ½¸ö»òÕ߸ü¶àÊôÐÔ×é³É£©¡£Õâ²»ÊÇÎÒÃǽ¨ÒéµÄ·½·¨£¬µ«ÊÇÈç¹ûÄãÏëÕâô×ö£¬ÕâÒ²ÊÇ¿ÉÄܵģº

class Person {
  String firstName
  String lastName

static mapping = { id composite:['firstName', 'lastName'] } }

ÉÏÃæµÄ´úÂ뽫ͨ¹ýPersonÀàµÄfirstNameºÍlastNameÊôÐÔÀ´´´½¨Ò»¸ö¸´ºÏid¡£µ±ÄãºóÃæÐèҪͨ¹ýidȡһ¸öʵÀýʱ£¬Äã±ØÐëÓÃÕâ¸ö¶ÔÏóµÄÔ­ÐÍ

def p = Person.get(new Person(firstName:"Fred", lastName:"Flintstone"))
println p.firstName

5.5.2.6 Êý¾Ý¿âË÷Òý

ΪµÃµ½×îºÃµÄ²éѯÐÔÄÜ£¬Í¨³£ÄãÐèÒªµ÷Õû±íµÄË÷Òý¶¨Òå¡£ÈçºÎµ÷ÕûËüÃÇÊǸúÌØ¶¨ÁìÓòºÍÒª²éѯµÄÓ÷¨Ä£Ê½Ïà¹ØµÄ¡£Ê¹ÓÃGORMµÄDSLÄã¿ÉÒÔÖ¸¶¨ÄǸöÁÐÐèÒªË÷Òý

class Person {
  String firstName
  String address
  static mapping = {
      table 'people'
      version false
      id column:'person_id'
      firstName column:'First_Name', index:'Name_Idx'
      address column:'Address', index:'Name_Idx, Address_Index'
  }
}

5.5.2.7 ÀÖ¹ÛËøºÍ°æ±¾¶¨Òå

¾ÍÏñÔÚÀÖ¹ÛËøºÍ±¯¹ÛËø²¿·ÖÌÖÂ۵ģ¬Ä¬ÈÏÇé¿öÏ£¬GORMʹÓÃÀÖ¹ÛËøºÍÔÚÿһ¸öÀàÖÐ×Ô¶¯×¢ÈëÒ»¸översionÊôÐÔ£¬´ËÊôÐÔ½«Ó³ÉäÊý¾Ý¿âÖеÄÒ»¸översionÁÐ

Èç¹ûÄãÓ³ÉäµÄÊÇÒ»¸öÒÅÁôÊý¾Ý¿â£¨ÒѾ­´æÔÚµÄÊý¾Ý¿â--ÒëÕß×¢£©£¬Õ⽫ÊÇÒ»¸öÎÊÌ⣬Òò´Ë¿ÉÒÔͨ¹ýÈçÏ·½·¨À´¹Ø±ÕÕâ¸ö¹¦ÄÜ£º

class Person {
  ..
  static mapping = {
      table 'people'
      version false
  }
}

Èç¹ûÄã¹Ø±ÕÁËÀÖ¹ÛËø£¬Ä㽫×Ô¼º¸ºÔð²¢·¢¸üв¢ÇÒ´æÔÚÓû§¶ªÊ§Êý¾ÝµÄ·çÏÕ£¨³ý·ÇÄãʹÓñ¯¹ÛËø£©¡£

5.5.2.8 Á¢¼´¼ÓÔØºÍÑÓ³Ù¼ÓÔØ

ÑÓ³Ù¼ÓÔØ¼¯ºÏ

¾ÍÏñÔÚÁ¢¼´¼ÓÔØºÍÑÓ³Ù¼ÓÔØ²¿·ÖÌÖÂ۵ģ¬Ä¬ÈÏÇé¿öÏ£¬GORM ¼¯ºÏʹÓÃÑÓ³Ù¼ÓÔØµÄ²¢ÇÒ¿ÉÒÔͨ¹ýfetchModeÀ´ÅäÖà ¡£µ«Èç¹ûÄã¸üϲ»¶°ÑÄãËùÓеÄÓ³Éä¶¼¼¯ÖÐÔÚmappings´úÂë¿éÖУ¬ÄãÒ²¿ÉÒÔʹÓÃORMµÄDSLÀ´ÅäÖûñȡģʽ£º

class Person {
  String firstName
  static hasMany = [addresses:Address]
  static mapping = {
      addresses lazy:false
  }
}
class Address {
  String street
  String postCode
}

ÑÓ³Ù¼ÓÔØµ¥Ïò¹ØÁª

ÔÚGORMÖУ¬one-to-oneºÍmany-to-one¹ØÁªÈ±Ê¡ÊÇ·ÇÑÓ³Ù¼ÓÔØµÄ¡£ÕâÔÚÓкܶàʵÌ壨Êý¾Ý¿â¼Ç¼-ÒëÕß×¢£©µÄʱºò£¬»á²úÉúÐÔÄÜÎÊÌ⣬ÓÈÆäÊǹØÁª²éѯÊÇÒÔеÄSELECTÓï¾äÖ´ÐеÄʱºò£¬´ËʱÄãÓ¦¸Ã½«one-to-oneºÍmany-to-one¹ØÁªµÄÑÓ³Ù¼ÓÔØÏ󼯺ÏÄÇÑù½øÐÐÉèÖÃ:

class Person {
	String firstName
	static belongsTo = [address:Address]
	static mapping = {
		address lazy:true // lazily fetch the address
	}
}
class Address {
	String street
	String postCode
}

ÕâÀïÎÒÃÇÉèÖà PersonµÄaddressÊôÐÔΪÑÓ³Ù¼ÓÔØ

5.6 ÊÂÎñ±à³Ì

GrailsÊǹ¹½¨ÔÚSpringµÄ»ù´¡Éϵģ¬ËùÒÔʹÓÃSpringµÄÊÂÎñÀ´³éÏó´¦ÀíÊÂÎñ±à³Ì£¬µ«GORMÀàͨ¹ýwithTransaction·½·¨Ê¹µÃ´¦Àí¸ü¼òµ¥£¬·½·¨µÄµÚÒ»¸ö²ÎÊýÊÇSpringµÄTransactionStatus¶ÔÏó

µäÐ͵ÄʹÓó¡¾°ÈçÏ£º

def transferFunds = {
	Account.withTransaction { status ->
		def source = Account.get(params.from)
		def dest = Account.get(params.to)

def amount = params.amount.toInteger() if(source.active) { source.balance -= amount if(dest.active) { dest.amount += amount } else { status.setRollbackOnly() } }

}

}

ÔÚÉÏÃæµÄÀý×ÓÖУ¬Èç¹ûÄ¿µÄÕË»§Ã»Óд¦Óڻ״̬£¬ÏµÍ³½«»Ø¹öÊÂÎñ£¬Í¬Ê±Èç¹ûÓÐÈκÎÒì³£Å׳öÔÚÊÂÎñµÄ´¦Àí¹ý³ÌÖÐÒ²½«»á×Ô¶¯»Ø¹ö¡£

¼ÙÈçÄã²»Ïë»Ø¹öÕû¸öÊÂÎñ£¬ÄãÒ²¿ÉÒÔʹÓÃ"save points"À´»Ø¹öÒ»¸öÊÂÎñµ½Ò»¸öÌØ¶¨µÄµã¡£Äã¿ÉÒÔͨ¹ýʹÓÃSpringµÄSavePointManager½Ó¿ÚÀ´´ïµ½Õâ¸öÄ¿µÄ¡£

withTransaction·½·¨ÎªÄã´¦Àíbegin/commit/rollback´úÂë¿é×÷ÓÃÓòÄÚµÄÂß¼­¡£

5.7 GORMºÍÔ¼Êø

¾¡¹ÜÔ¼ÊøÊÇÑéÖ¤Õ½ڵÄÄÚÈÝ£¬µ«ÊÇÔÚ´ËÉæ¼°µ½Ô¼ÊøÒ²ÊǺÜÖØÒªµÄ£¬ÒòÎªÒ»Ð©Ô¼Êø»áÓ°Ïìµ½Êý¾Ý¿âµÄÉú³É¡£

Grailsͨ¹ýʹÓÃÁìÓòÀàµÄÔ¼ÊøÀ´Ó°ÏìÊý¾Ý¿â±í×ֶΣ¨ÁìÓòÀàËù¶ÔÓÚµÄÊôÐÔ£©µÄÉú³É£¬»¹ÊÇ¿ÉÐеġ£

¿¼ÂÇÏÂÃæµÄÀý×Ó£¬¼ÙÈçÎÒÃÇÓÐÒ»¸öÓòÄ£ÐÍÈçϵÄÊôÐÔ£º

String description

ĬÈÏÇé¿öÏ£¬ÔÚMySqlÊý¾Ý¿âÖУ¬Grails½«»á¶¨ÒåÕâ¸öÁÐΪ

column name | data type 
 description | varchar(255)

µ«ÊÇ£¬ÔÚÒµÎñ¹æÔòÖУ¬ÒªÇóÕâ¸öÁìÓòÀàµÄdescriptionÊôÐÔÄܹ»ÈÝÄÉ1000¸ö×Ö·û£¬ÔÚÕâÖÖÇé¿öÏ£¬Èç¹ûÎÒÃÇÊÇʹÓÃSQL½Å±¾£¬ÄÇôÎÒÃǶ¨ÒåµÄÕâ¸öÁпÉÄÜÊÇ£º

column name | data type 
 description | TEXT

ÏÖÔÚÎÒÃÇÓÖÏëÒªÔÚ»ùÓÚÓ¦ÓóÌÐòµÄ½øÐÐÑéÖ¤£¬_ÒªÇóÔڳ־û¯ÈκμǼ֮ǰ_£¬È·±£²»Äܳ¬¹ý1000¸ö×Ö·û¡£ÔÚGrailsÖУ¬ÎÒÃÇ¿ÉÒÔͨ¹ýÔ¼ÊøÀ´Íê³É£¬ÎÒÃǽ«ÔÚÁìÓòÀàÖÐÐÂÔöÈçϵÄÔ¼ÊøÉùÃ÷£º

static constraints = {
        description(maxSize:1000)
}

Õâ¸öÔ¼ÊøÌõ¼þ½«»áÌṩÎÒÃÇËùÐèµÄ»ùÓÚÓ¦ÓóÌÐòµÄÑéÖ¤²¢ÇÒÒ²½«Éú³ÉÉÏÊöʾÀýËùʾµÄÊý¾Ý¿âÐÅÏ¢¡£ÏÂÃæÊÇÓ°ÏìÊý¾Ý¿âÉú³ÉµÄÆäËûÔ¼ÊøµÄÃèÊö¡£

Ó°Ïì×Ö·û´®ÀàÐÍÊôÐÔµÄÔ¼Êø

Èç¹ûmaxSize»òÕßsizeÔ¼Êø±»¶¨Ò壬Grails½«¸ù¾ÝÔ¼ÊøµÄÖµÉèÖÃÁеÄ×î´ó³¤¶È¡£

ͨ³££¬²»½¨ÒéÔÚͬһ¸öµÄÁìÓòÀàÖÐ×éºÏʹÓÃÕâÐ©Ô¼Êø¡£µ«ÊÇ£¬Èç¹ûÄã·ÇҪͬʱ¶¨ÒåmaxSizeºÍsizeÔ¼ÊøµÄ»°£¬Grails½«ÉèÖÃÁеij¤¶ÈΪmaxSizeÔ¼ÊøºÍsizeÉÏÏÞÔ¼ÊøµÄ×îÉÙÖµ£¨GrailsʹÓÃÁ½ÕßµÄ×îÉÙÖµ£¬Òò´ËÈκγ¬¹ý×îÉÙÖµµÄ³¤¶È½«µ¼ÖÂÑéÖ¤´íÎó£©

Èç¹û¶¨ÒåÁËinListÔ¼Êø£¨maxSizeºÍsize䶨Ò壩µÄ»°£¬×Ö¶Î×î´ó³¤¶È½«È¡¾öÓÚÁÐ±í£¨list£©ÖÐ××Ö·û´®µÄµÄ³¤¶È¡£ÒÔ"Java"¡¢"Groovy"ºÍ"C++"ΪÀý£¬Grails½«ÉèÖÃ×ֶεij¤¶ÈΪ6£¨"Groovy"µÄ×º¬ÓÐ6¸ö×Ö·û£©¡£

Ó°ÏìÊýÖµÀàÐÍÊôÐÔµÄÔ¼Êø

Èç¹û¶¨ÒåÁËÔ¼Êømax¡¢min»òÕßrange£¬Grails½«»ùÓÚÔ¼ÊøµÄÖµ³¢ÊÔ×ÅÉèÖÃÁеľ«¶È£¨ÉèÖõĽá¹ûºÜ´ó³Ì¶ÈÉÏÒÀÀµÓÚHibernate¸úµ×²ãÊý¾Ý¿âϵͳµÄ½»»¥³Ì¶È£©¡£

ͨ³£À´Ëµ£¬²»½¨ÒéÔÚͬһÁìÓòÀàµÄÊôÐÔÉÏ×éºÏ³ÉË«µÄmin/maxºÍrangeÔ¼Êø£¬µ«ÊÇÈç¹ûÕâÐ©Ô¼ÊøÍ¬Ê±±»¶¨ÒåÁË£¬ÄÇôGrails½«Ê¹ÓÃÔ¼ÊøÖµÖеÄ×îÉÙ¾«¶ÈÖµ£¨GrailsÈ¡Á½ÕßµÄ×îÉÙÖµ£¬ÊÇÒòΪÈÎÒⳬ¹ý×îÉÙ¾«¶ÈµÄ³¤¶È½«»áµ¼ÖÂÒ»¸öÑéÖ¤´íÎ󣩡£

Èç¹û¶¨ÒåÁËscaleÔ¼Êø£¬ÄÇôGrails»áÊÔͼʹÓûùÓÚÔ¼ÊøµÄÖµÀ´ÉèÖÃÁеıê¶È£¨scale£©¡£´Ë¹æÔò½ö½öÓ¦ÓÃÓÚ¸¡µ