关于ssme这个我的小示例项目,想做到麻雀虽小,五脏俱全,看到很多一些web都有定时发送邮件的功能,想我ssme也加入一下这种功能,经查询相关文档,发现spring本身自带了一个调度器quartz,下面就来试试用这个东西来实现一下自动发送一个本地化了出件出来,请注意两个重点特色,一个是自动,另一个邮件本地化。

说一段题外话, 以前一直嫌Java麻烦,不好弄环境,写个什么东西测试要搞这配那的,这不今天,我想回顾一下AOP的实现,用最简单的InvocationHandler接口,却发现也没有以前那么麻烦了。几分钟上的Demo源码就出来了,写写小程序有时候是不是一种小乐趣呢,你怎么认为?

mvn archetype:generate -DgroupId=com.vanceinfo.javaserial -Dar
tifactId=myaop -DarchetypeArtifactId=maven-archetype-quickstart -DpackageName=co
m.vanceinfo.javaserial

maven-archetype-quickstart

public interface IAnimal {
public void run();
public void jump();
}

动物接口可跑可跳

public class Dog implements IAnimal{
public void run() {
System.out.println("Dog Start running...");
}
public void jump() {
System.out.println("Dog Start jumping...");
}
}

动物小狗会跑会跳

public class MyProxy implements InvocationHandler {

    private Object targetObject;
public Object createInstance(Object targetObj){
this.targetObject=targetObj;
return Proxy.newProxyInstance(targetObj.getClass().getClassLoader(), targetObj.getClass().getInterfaces(),this);
}
private void beforeMethod(){
System.out .println("before method..");
} private void endMethod(){
System.out .println("End method..");
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
beforeMethod();
Object obj = method.invoke(targetObject, args);
endMethod();
return obj;
}
}

代理实现InvocationHandler

public class App
{
public static void main( String[] args )
{
MyProxy myProxy = new MyProxy();
IAnimal animal =(IAnimal) myProxy.createInstance(new Dog());
animal.run();
animal.jump();
}
}

客户端调用代理创建具体动物小狗

上面的这段小代码,纯粹是我一时兴起,不想另开一篇,强行插入的哈。书归正卷,回到今天的主题。

1,首先引用POM

<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
</dependency>

quartz2.2.1

今天在程序中偶然发现当我用@Resource标签时,Eclipse给了我一个提示了:

Access restriction: The type Resource is not accessible due to restriction on required library E:\Java\jdk1.7.0_25\jre\lib\rt.jar

百思不得其解,其实去到properties里面删掉JRE System Library,然后再加回来就OK了,即

1,Go to the Build Path settings in the project properties.
2,Remove the JRE System Library
3,Add it back; Select "Add Library" and select the JRE System Library. The default worked for me.

国外人给的答案

2,配置文件添加片断

     <!-- scheduling taskExecutor -->
<bean id="schedulingEmailTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="5" />
<property name="maxPoolSize" value="10" />
<property name="queueCapacity" value="25" />
</bean> <bean id="schedulingEmailJob" class="com.vanceinfo.javaserial.scheduling.SchedulingEmailJob"/> <bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="schedulingEmailJob" />
<property name="targetMethod" value="execute" />
</bean>
<!--<bean id="schedulingCronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean"> -->
<bean id="schedulingCronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="jobDetail" />
<property name="cronExpression" value="${com.vanceinfo.scheduling/jobCronExpression}" />
</bean> <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="schedulingCronTrigger" />
</list>
</property>
</bean>

scheduling taskExecutor

注意第16行我注释的那一行,<bean id="schedulingCronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">,在1.x下面,很多都是这样子配置CronTrigger,以致于现在好多同学,都认为只能用低版本的Quartz,比方说1.8.4. 甚至有人还分析说:

Caused by: org.springframework.beans.factory.CannotLoadBeanClassException: Error loading class [org.springframework.scheduling.quartz.CronTriggerBean] for bean with name 'mytrigger' defined in class path resource [applicationContext.xml]: problem with class file or dependent class; nested exception is java.lang.IncompatibleClassChangeError: class org.springframework.scheduling.quartz.CronTriggerBean has interface org.quartz.CronTrigger as super class

查看发现spring3.0.5中org.springframework.scheduling.quartz.CronTriggerBean继承了org.quartz.CronTrigger(public class CronTriggerBeanextends CronTrigger),而在quartz2.0.2中org.quartz.CronTrigger是个接口(publicabstract interface CronTrigger extends Trigger),而在quartz1.8.5及1.8.4中org.quartz.CronTrigger是个类(publicclass CronTrigger extends Trigger),从而造成无法在applicationContext中配置触发器。这是spring3.0.5和quartz2.0.2版本不兼容的一个bug。

使用quartz2.2.1的出错信息

实则不然,在查看CronTriggerBean的API时有句话写得是:

NOTE: This convenience subclass does not work against Quartz 2.0. Use Quartz 2.0's native JobDetailImpl class or the new Quartz 2.0 builder API instead. Alternatively, switch to Spring's CronTriggerFactoryBean which largely is a drop-in replacement for this class and its properties and consistently works against Quartz 1.x as well as Quartz 2.x.

这个说明一件事情,有的时候,官网上的资料才是最权威,也是解决问题最直接的地方,对于咱们中国人来说,也说明另一件事就是要学好英语,如果这里是一段中文注意在这里,相信网上这些说要用低版本的Quartz估计就没有人不喷了。

3,看到配置,很明显,我们要有一个SchedulingEmailJob类。比较简单,贴代码

package com.vanceinfo.javaserial.scheduling;

import java.util.Date;

import org.apache.log4j.Logger;

import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.TaskExecutor; import com.vanceinfo.javaserial.services.IUserService; public class SchedulingEmailJob { @Resource(name = "schedulingEmailTaskExecutor")
private TaskExecutor taskExecutor; private IUserService userService; /**
* @return the userService
*/
public IUserService getUserService() {
return userService;
} /**
* @param userService the userService to set
*/
@Autowired
public void setUserService(IUserService userService) {
this.userService = userService;
} /**
* @return Returns the taskExecutor.
*/
public TaskExecutor getTaskExecutor() {
return taskExecutor;
} /**
* @param taskExecutor The taskExecutor to set.
*/
public void setTaskExecutor(TaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
} private static Logger LOGGER = Logger.getLogger(SchedulingEmailJob.class); public void execute() {
Date taskStartTime = new Date();
LOGGER.info("Scheduled sending email task start! Server date time: " + taskStartTime);
taskExecutor.execute(new SchedulingEmailRunnable(userService));
Date taskEndTime = new Date();
LOGGER.info("Scheduled sending email task finish! Server date time: " + taskEndTime);
} }

SchedulingEmailJob

至于SchedulingEmailRunnable嘛,实际上就是在run里面调用我们前面的userService里面的方法来发送邮件

package com.vanceinfo.javaserial.scheduling;

import com.vanceinfo.javaserial.services.IUserService;

public class SchedulingEmailRunnable implements Runnable {

    private IUserService userService;

    public SchedulingEmailRunnable(IUserService userService){
this.userService = userService;
} public void run() {
userService.getUserById(1, true);
} }

SchedulingEmailRunnable

由于这里我只是演示,所以直接传了个用户id=1,实际应用中这里应该接合数据库或别的什么来得到邮件地址,然后一个一个发送出去。引申出去,我们完全可以做一个auto-bot的东东出来。

4,关于cronExpression,心中默记6位,用空格来格开。秒分时日月年,顺序决不能混。但好象有人总结也有7位的。

* * * * * ? 
- - - - - -
| | | | | |
| | | | | +----- day of week (MON-SUN) 
| | | | +------- month (1 - 12)
| | | +--------- day of month (1 - 31)
| | +----------- hour (0 - 23)
| +------------- min (0 - 59)
+------------- sec (0 - 59)

看这位兄弟的总结

一个cronExpression表达式有至少6个(也可能是7个)由空格分隔的时间元素。从左至右,这些元素的定义如下:

1.秒(0–59)

2.分钟(0–59)

3.小时(0–23)

4.月份中的日期(1–31)

5.月份(1–12或JAN–DEC)

6.星期中的日期(1–7或SUN–SAT)

7.年份(1970–2099)

0 0 10,14,16 * * ?

每天上午10点,下午2点和下午4点

0 0,15,30,45 * 1-10 * ?

每月前10天每隔15分钟

30 0 0 1 1 ? 2012

在2012年1月1日午夜过30秒时

0 0 8-5 ? * MON-FRI

每个工作日的工作时间

各个时间可用值如下:

秒 0-59 , - * /

分 0-59 , - * /

小时 0-23 , - * /

日 1-31 , - * ? / L W C

月 1-12 or JAN-DEC , - * /

周几 1-7 or SUN-SAT , - * ? / L C #

年 (可选字段) empty, 1970-2099 , - * /

可用值详细分析如下:

“*”——字符可以用于所有字段,在“分”字段中设为"*"表示"每一分钟"的含义。

“?”——字符可以用在“日”和“周几”字段. 它用来指定 '不明确的值'. 这在你需要指定这两个字段中的某一个值而不是另外一个的时候会被用到。在后面的例子中可以看到其含义。

“-”——字符被用来指定一个值的范围,比如在“小时”字段中设为"10-12"表示"10点到12点"。

“,”——字符指定数个值。比如在“周几”字段中设为"MON,WED,FRI"表示"the days Monday, Wednesday, and Friday"。

“/”——字符用来指定一个值的的增加幅度. 比如在“秒”字段中设置为"0/15"表示"第0, 15, 30, 和 45秒"。而 "5/15"则表示"第5, 20, 35, 和 50". 在'/'前加"*"字符相当于指定从0秒开始. 每个字段都有一系列可以开始或结束的数值。对于“秒”和“分”字段来说,其数值范围为0到59,对于“小时”字段来说其为0到23, 对于“日”字段来说为0到31, 而对于“月”字段来说为1到12。"/"字段仅仅只是帮助你在允许的数值范围内从开始"第n"的值。

“L”——字符可用在“日”和“周几”这两个字段。它是"last"的缩写, 但是在这两个字段中有不同的含义。例如,“日”字段中的"L"表示"一个月中的最后一天" —— 对于一月就是31号对于二月来说就是28号(非闰年)。而在“周几”字段中, 它简单的表示"7" or "SAT",但是如果在“周几”字段中使用时跟在某个数字之后, 它表示"该月最后一个星期×" —— 比如"6L"表示"该月最后一个周五"。当使用'L'选项时,指定确定的列表或者范围非常重要,否则你会被结果搞糊涂的。

“W”——可用于“日”字段。用来指定历给定日期最近的工作日(周一到周五) 。比如你将“日”字段设为"15W",意为: "离该月15号最近的工作日"。因此如果15号为周六,触发器会在14号即周五调用。如果15号为周日, 触发器会在16号也就是周一触发。如果15号为周二,那么当天就会触发。然而如果你将“日”字段设为"1W", 而一号又是周六, 触发器会于下周一也就是当月的3号触发,因为它不会越过当月的值的范围边界。'W'字符只能用于“日”字段的值为单独的一天而不是一系列值的时候。

“L”和“W”可以组合用于“日”字段表示为'LW',意为"该月最后一个工作日"。

“#”—— 字符可用于“周几”字段。该字符表示“该月第几个周×”,比如"6#3"表示该月第三个周五( 6表示周五而"#3"该月第三个)。再比如: "2#1" = 表示该月第一个周一而 "4#5" = 该月第五个周三。注意如果你指定"#5"该月没有第五个“周×”,该月是不会触发的。

“C”—— 字符可用于“日”和“周几”字段,它是"calendar"的缩写。 它表示为基于相关的日历所计算出的值(如果有的话)。如果没有关联的日历, 那它等同于包含全部日历。“日”字段值为"5C"表示"日历中的第一天或者5号以后",“周几”字段值为"1C"则表示"日历中的第一天或者周日以后"。

对于“月份”字段和“周几”字段来说合法的字符都不是大小写敏感的。

一些例子:

"0 0 12 * * ?" 每天中午十二点触发 
"0 15 10 ? * *" 每天早上10:15触发 
"0 15 10 * * ?" 每天早上10:15触发 
"0 15 10 * * ? *" 每天早上10:15触发 
"0 15 10 * * ? 2005" 2005年的每天早上10:15触发 
"0 * 14 * * ?" 每天从下午2点开始到2点59分每分钟一次触发 
"0 0/5 14 * * ?" 每天从下午2点开始到2:55分结束每5分钟一次触发 
"0 0/5 14,18 * * ?" 每天的下午2点至2:55和6点至6点55分两个时间段内每5分钟一次触发 
"0 0-5 14 * * ?" 每天14:00至14:05每分钟一次触发 
"0 10,44 14 ? 3 WED" 三月的每周三的14:10和14:44触发 
"0 15 10 ? * MON-FRI" 每个周一、周二、周三、周四、周五的10:15触发 
"0 15 10 15 * ?" 每月15号的10:15触发 
"0 15 10 L * ?" 每月的最后一天的10:15触发 
"0 15 10 ? * 6L" 每月最后一个周五的10:15

5, 进行到这里,我们似乎忘记了还有一个特点没有讲解,就是邮件的多国语言了。由于我要将一些字串转化成Locale,即LocaleUtils.toLocale("Str")所以,我需要再引入一个依赖

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.1</version>
</dependency>

commons-lang3_3.1

接着在SendEmailImpl中加入/修改以下两句

//这句很关键,因为象我们中国语言之类的,如果 encoding不对时,是会显示乱码的。
MimeMessageHelper message = new MimeMessageHelper(mimeMessage, true,"UTF-8"); //这里只是一个示例,字串zh_CN我们应该根据页面语言获得
model.put("locale",org.apache.commons.lang3.LocaleUtils.toLocale("zh_CN"));

SendEmailImpl

当然messageSource和locale还是要放进model里的

//这一句不解释,接合我前面的系列来看,可以明白为什么直接可以这样子写
@Resource(name = "messageSource")
private MessageSource messageSource; //另外在还要加上以下两句。
model.put("messages", this.messageSource);
model.put("locale",org.apache.commons.lang3.LocaleUtils.toLocale("zh_CN"));

第三步,在zh_CN里加入两条记录

email.Bullet1_Name=注意, {0}. 您获得了以下物品:
email.Bullet2_Email=您的邮件地址是 {0}.

并且在我的邮件模板中加入

#set($userName = ["${emailPlaceHolder.name}"])
#set($userEmail=["${emailPlaceHolder.email}"]) <p class="emailTagline">$messages.getMessage("email.Bullet1_Name",$userName.toArray(), $locale)</p>
<p>$messages.getMessage("email.Bullet2_Email",$userEmail.toArray(), $locale)</p>

myEmail.vm

关于vm的语法,可以看这里。当然,我个人的印象最深的就是vm里没有for(int=0;i<n;i++)之类的。判断一个对象为空的语法是“”==$user, 贴一个我曾经印象最深的vm模板在这里。

<html>
<head>
<style type="text/css">
body {
margin: 0 auto;
max-width: 900px;
font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
font-size: 1em;
line-height: 1.2em;
background-color: #F5FFFF;
color: #333;
}
a,a:visited {
text-decoration: none;
color: #006699;
}
a:hover {
color: #EC891D;
}
.emailTagline {
margin-top: 2.6em;
font-size: 1.2em;
}
</style> </head> <body>
<a href="http://www.expediapartnercentral.com" style="outline:none;"><img src="cid:logoName"/></a>
#set($userName = ["${emailPlaceHolder.userName}"])
#set($discountStr = ["${emailPlaceHolder.drrInfo.discountStr}%"])
#set($bookStartDateStr = ["${emailPlaceHolder.drrInfo.bookStartDateStr}"])
#set($travelDatesStr = ["${emailPlaceHolder.drrInfo.travelStartDateStr}","${emailPlaceHolder.drrInfo.travelEndDateStr}"])
#set($promotionCode=["${emailPlaceHolder.drrInfo.notificationCode}"]) #if(""==$emailPlaceHolder.drrInfo.ratePlans)
#set($promotionroomsrates=[" "])
#else
#set($promotionroomsrates=["<table width='750' border='0' cellspacing='0' cellpadding='1'>
<tr><td width='240' height='30'>$messages.getMessage('roomTypeLbl',null,$locale)</td>
<td width='250'>$messages.getMessage('ratePlanLbl',null,$locale)</td>
<td width='250'>$messages.getMessage('rateTypeLbl',null,$locale)</td></tr>
#set($index=0)
#foreach($ratePlan in $emailPlaceHolder.drrInfo.ratePlans)
#if($index%2==0)
<tr style='background-color: #ebf5fb'>
<td height='30'>$ratePlan.roomType</td>
<td>$ratePlan.ratePlanName</td>
<td>$ratePlan.ratePlanType</td>
</tr>
#else
<tr>
<td height='30'>$ratePlan.roomType</td>
<td>$ratePlan.ratePlanName</td>
<td>$ratePlan.ratePlanType</td>
</tr>
#end
#set($index=$index+1)
#end
</table>"])
#end #if(""==$emailPlaceHolder.drrInfo.blackOutDates)
#set($blackoutdates=["$messages.getMessage('noBlackoutInfo',null, $locale)"])
#else
#set($blackoutdates=["<ul>
#foreach($blackOut in $emailPlaceHolder.drrInfo.blackOutDates)
#if($blackOut.startDate==$blackOut.endDate)
<li><span>$blackOut.startDateStr</span></li>
#else
<li><span>$blackOut.startDateStr</span><span>$messages.getMessage('travelDates_To',null, $locale)</span><span>$blackOut.endDateStr</span></li>
#end
#end
</ul>"])
#end <p class="emailTagline">$messages.getMessage("dotdWonAuction_1",$userName.toArray(), $locale)</p> <p>$messages.getMessage("dotdWonAuction_2",$bookStartDateStr.toArray(), $locale)</p> <p>$messages.getMessage("dotdWonAuction_3",null, $locale)</p> <p>$messages.getMessage("dotdWonAuction_Bullet1_Name",$promotionCode.toArray(), $locale)</p> <p>$messages.getMessage("dotdWonAuction_Bullet2_Discount",$discountStr.toArray(), $locale)</p> <p>$messages.getMessage("dotdWonAuction_Bullet3_BookingDates",$bookStartDateStr.toArray(), $locale)</p> <p>$messages.getMessage("dotdWonAuction_Bullet4_TravelDates",$travelDatesStr.toArray(), $locale)</p> <p>$messages.getMessage("dotdWonAuction_Bullet5_AppliesTo",$promotionroomsrates.toArray(), $locale)</p> <p>$messages.getMessage("dotdWonAuction_Bullet6_BlackoutDays",$blackoutdates.toArray(), $locale)</p> </body>
</html>

我印象深刻的一个我曾写的第一个vm模板

好了,运行程序,1分钟之后,我收到的邮件内容如下:

Java系列--第八篇 基于Maven的SSME之定时邮件发送的更多相关文章

  1. Java系列--第四篇 基于Maven的SSME之发送邮件

    在系列第一篇中,使用的是mybatis得到了一个小小的项目,而该项目的用户对象是有邮件地址的,如果按照邮件地址给对方去一封邮件会不会更能体现针对性呢,所以,我在这篇准备加入发送邮件的功能,利用的就是s ...

  2. Java系列--第五篇 基于Maven的SSME之Token及Parameterized单元测试

    本来在第四篇要说完的,但是写着写着,我觉得内容有点多起来了,所以就另开这篇,在这里专门讲述Token的定义,JSP自定义标签以及如何用Parameterized的来做单元测试. 1,新建包com.va ...

  3. Java系列--第六篇 基于Maven的SSME之多国语言实现

    如果你的网站足够强大,以致冲出了国门,走向了国际的话,你就需要考虑做多国语言了,不过,未雨绸缪,向来是我辈程序人员的优秀品质,谁知道那天,我们的网站被国外大公司看中收购,从而飞上枝头变凤凰.不扯这么多 ...

  4. Java系列--第七篇 基于Maven的Android开发实战项目

    本篇是基于<Android应用案例开发大全,吴亚峰等著>的项目开发实例源码,其中有些图片,我做了一些修改,用于个人学习,请勿用于商业. 1, 日程管理专家 mvn archetype:ge ...

  5. Java系列--第三篇 基于Maven的Android开发CAIO

    学习要打好基础,这里用一个项目来学习一下Android的组件,参考网址为这个但不限于这个.有些东西的学习,理解三遍理论还不如一遍操作,所谓理论来自实践,实践是检验真理的唯一标准.所以,虽然看懂了那篇文 ...

  6. Java系列--第二篇 基于Maven的Android开发HelloAndroidWorld

    曾经写过一篇Android环境配置的随笔,个人感觉特繁琐,既然有Maven,何不尝试用用Maven呢,经网上搜索这篇文章但不限于这些,而做了一个基于Maven的Android版的Hello Andro ...

  7. java selenium (三) 环境搭建 基于Maven

    现在Java的大部分项目都是基于Maven,  在Maven项目中使用Selenium2. 非常简单. 首先你需要配置好Maven的环境 可以参考本博客的Maven教程系列,Maven入门教程(一) ...

  8. 【第十八篇】- Maven Eclipse之Spring Cloud直播商城 b2b2c电子商务技术总结

    Maven Eclipse Eclipse 提供了一个很好的插件 m2eclipse ,该插件能将 Maven 和 Eclipse 集成在一起. 在最新的 Eclipse 中自带了 Maven,我们打 ...

  9. SQL注入之Sqli-labs系列第八篇(基于布尔盲注的注入)

    开始挑战第八关(Blind- Boolian- Single Quotes- String) 这关首先需要用到以下函数: 开始测试,使用测试语句,利用单引号进行闭合 猜解字段 union select ...

随机推荐

  1. Android修改XML文件

    最近在项目中需要使用XML记录数据,网上这方面的文章较少,记录一下 使用DOM方式 /** * 追加内容到XML文档 * @param instructions * @throws ParserCon ...

  2. C#控制生成图片的大小

    private void button1_Click(object sender, EventArgs e) { using (Bitmap bitmap = new Bitmap("d:\ ...

  3. delphi 程序窗体及控件自适应分辨率(通过ComponentCount遍历改变字体大小以及上下左右)

    unit untFixForm; interface uses Classes, SysUtils, Controls, Forms; type TFontedControl = class(TCon ...

  4. 【转】我的Android笔记(十)—— ProgressDialog的简单应用,等待提示

    原文网址:http://blog.csdn.net/barryhappy/article/details/7376231 在应用中经常会用到一些费时的操作,需要用户进行等待,比如加载网页内容…… 这时 ...

  5. 2014-07-30 MVC框架中对SQL Server数据库的访问

    今天是在吾索实习的第16天.我自己主要学习了基于MVC框架的系统的开发时,对SQL Server数据库的相关访问.其步骤如下: 第一步,在Models文件夹中创建一个类,并命名为Movies.cs,如 ...

  6. guestfish 修改 image file

    Example guestfish sessionSometimes, you must modify a virtual machine image to remove any traces of ...

  7. Java实现生命周期管理机制

    先扯再说 最近一直在研究某个国产开源的MySQL数据库中间件,拉下其最新版的代码到eclipse后,启动起来,然后做各种测试和代码追踪:用完想要关闭它时,拉出它的STOP类想要运行时,发现这个类里赫然 ...

  8. chomp方法

    chomp方法属于String类里面的: "hello".chomp #=> "hello" "hello\n".chomp #=&g ...

  9. ORACLE_CLASS_ENDING

    [JSU]LJDragon's Oracle course notes In the first semester, junior year Oracle考前复习 试题结构分析: 1.选择题2x10, ...

  10. PHP设计模式笔记六:数据对象映射模式 -- Rango韩老师 http://www.imooc.com/learn/236

    数据对象映射模式 1.数据对象映射模式,是将对象和数据存储映射起来,对一个对象的操作会映射为对数据存储的操作 2.在代码中实现数据对象映射模式,我们将实现一个ORM类,将复杂的SQL语句映射成对象属性 ...