帐前卒专栏

code, software architect, articles and novels.
代码,软件架构,博客和小说

前一段时间在写分页语句。单机的mysql一般使用:

1
select * from [table] limit [length] offset [offset]

这里的问题在于offset如果很大,那么意味着你要扫描整个表。当然如果offset很小,那这个速度还是相当快的。当然替换方案是使用where子句而不使用offset.

1
select * from [table] where column > [pre_column_value] limit [length]

然而对于使用主键做了Range分区,hash分区,或者使用了mysql搭建的分布式数据库。这里的问题在于分区的索引是局部的,而不是全局的。对于limit和offset操作不光要获取到更多的数据,而言看起来也不是很安全。所以建议还是使用对主键进行order by。SQL语句是这样:

1
select * from [table] where key_column > [pre_key_column_value] order by key_column limit [length]

这样才是最保险的.

首先看一个存储过程,这个存储过程是为了构造数据使用的。当然贴出来的存储过程简化了一些不必要的表结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

CREATE PROCEDURE modifyRootEntry()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE userId INT;
DECLARE userIdIter CURSOR FOR SELECT DISTINCT user_id from entries;
OPEN userIdIter;

read_loop: LOOP
FETCH userIdIter INTO userId;
IF done THEN
LEAVE read_loop;
END IF;
INSERT INTO entries (id, name, user_id, parent_id) VALUES(0, 'root_parent', userId, 0);
UPDATE entries SET parent_id=0 where user_id=userId AND name='file_root' AND parent_id is NULL;
END LOOP;

CLOSE userIdIter;
END;

这个存储过程运行了10分钟,好像仅插入了6w行记录。这个真是太慢了。首先一个问题,就是这里使用了隐式提交,所以这里需要写明transaction开始和commit.修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

CREATE PROCEDURE modifyRootEntry()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE userId INT;
DECLARE userIdIter CURSOR FOR SELECT DISTINCT user_id from entries;

START TRANSACTION; // here !
OPEN userIdIter;

read_loop: LOOP
FETCH userIdIter INTO userId;
IF done THEN
LEAVE read_loop;
END IF;
INSERT INTO entries (id, name, user_id, parent_id) VALUES(0, 'root_parent', userId, 0);
UPDATE entries SET parent_id=0 where user_id=userId AND name='file_root' AND parent_id is NULL;
END LOOP;

CLOSE userIdIter;
COMMIT;// here !
END;

还有一个问题,就是

1
UPDATE entries SET parent_id=0 where user_id=userId AND name='file_root' AND parent_id is NULL;

这句话应该放在LOOP的外面。因为每次执行的都一样。修改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CREATE PROCEDURE modifyRootEntry()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE userId INT;
DECLARE userIdIter CURSOR FOR SELECT DISTINCT user_id from entries;

START TRANSACTION; // here !
OPEN userIdIter;

read_loop: LOOP
FETCH userIdIter INTO userId;
IF done THEN
LEAVE read_loop;
END IF;
INSERT INTO entries (id, name, user_id, parent_id) VALUES(0, 'root_parent', userId, 0);

END LOOP;
UPDATE entries SET parent_id=0 where user_id=userId AND name='file_root' AND parent_id is NULL; // here!
CLOSE userIdIter;
COMMIT;// here !
END;

其实上面的语句仍然不够快,原因在于这个是一行行插入的。可以使用辅助表进行加速。从网上直接copy过来的。这里因为每次插入的是2^n个行,所以速度大大提高。辅助表的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE PROCEDURE pFastCreateNums (cnt INT UNSIGNED)
BEGIN
DECLARE s INT UNSIGNED DEFAULT 1;
TRUNCATE TABLE Nums;
INSERT INTO Nums SELECT s;
WHILE s*2 <= cnt DO
BEGIN
INSERT INTO Nums SELECT a+s FROM Nums;
SET s = s*2;
END;
END WHILE;
END;

上面的是生成一个数字列。当然我们没有必要生成这样一个数字列,但是思想完全可以通用。

1. 幻读。很多书和blog都提到mysql 的 tx_isolate (事务隔离)为repeatable read. 并且Mysql做了next-key lock的事情防止幻读。但是如果你直接在事务中使用

1
SELECT * from [table];

这还是会有幻读发生。只有你在SELECT语句中加入锁才能防止幻读。例如:

1
2
3
SELECT * from [table] for update;

SELECT * from [table] lock in share mode;

以上两句都可以防止幻读。第二句要优于第一句,因为加的是读锁而不是写锁。

另外介绍一下幻读:

幻读就是当一个事务进行查询时没有发现该记录,或者没有发现该记录被更改。结果在正在插入该记录或者真正要改该记录时,发现这条记录已经被其他事务改过了。

例如

1
2
3
4
5
6
事务1select * from [table];

这时进行事务2insert into [table] select xx; commit;
事务1 再执行 select * from [table]; 这里是没有 事务2 的数据的。

这里只要事务1再次执行 insert into [table] select xx; 这样就发现这个行已经存在了;这就是幻读。

2. 另外发现update的一个问题。如果使用事务, 首先select 数据然后再使用该数据进行update,有可能会有问题。一般使用 update [table] set col = col + value where …这种形式进行更新,比select col from [table]; 取到col然后再进行update [table] set col = 刚才select 出的col value + value; 这样更加正确,因为update自身可以加写锁。保证了不会幻读。

3. 另外一个问题涉及到 limit. 例如 select * from [table] limit [len] offset [offset]; 如果这里offset比较小,那么这样做是可以的。如果这里offset很大,那么这样做类似于扫表。这里需要使用where子句来加速。

昨天参加一下2013
PyCon。原本以为是python的小规模交流聚会。结果是GDG赞助的布道会。感觉起来还是交流太少,结识的人也少,只是听取思路和知识点。一句话:太耗时间。
首先介绍了一个叫做Leo的项目。这个项目看起来使用的Python的自完备IDE。即是开发Python的,也是用Python开发的。所有的代码都可以集成到Le
o上面。听起来不错。只是不知道开发效率怎么样?

然后是豆瓣开发了一个和Google App Engine很像的东西。叫做DAE。只能运行Python的APP。这样做的好处是方便部署,减少运维团队。豆瓣只有
4个SA。这个东西想想贵司也是做过的。只是正规的项目都难以迁移到那个上面,所以现在沦为了静态页面和简单website的聚集地。看起来应该豆瓣强迫所有的项目都
移植到那个上面吧。豆瓣的service会达到5K qps.这个service水平,听起来比词典要低好多好多呀…搜索了一下,豆瓣过去使用的是github的
企业版,看起来这两年估计觉得缴费太多才使用了gitlab自己搞了一套?我觉得应该不是豆瓣自己用python写了一套同样的gitlab吧?DAE这个系统开发了
两年,现在还是私有的。当然不知道他们用了多少个人做这个开发。这个DAE有一个不错的东西功能:灰度上线。不过对于基本上都是website的网站,可能这样做灰度
上线切一下nginx就好了。想想如果有多个端,怎么做灰度上线呢?特别是发布各个端APP的平台都是第三方的。这个就更难做了。DAE这个还有几点是可以借鉴的。第
一是内部的服务使用thrift,外部的服务使用HTTP。二是内部使用的UDP socket而不是TCP socket(这里…不知道对错)。

然后是Uliweb 一款python的web框架,现在的版本是0.9+,
看来在作者心目中仍然没有到1.0…我觉得这样的产品还是作者自己先玩玩。等升到1.0,我等再拿来看看。其不错的特点就是可以将css和JS压缩打包。

下面是Buildout,类似maven的一款依赖解决打包软件。为什么不用pip,作者说因为pip只能下载stable版本…自己查了一下pip的文档。自从
1.4版本后,pip就支持pre-release version和devlopment version. 下面是原文:

1
2
3
4
5
6
7
8

Pre-release Versions

Starting with v1.4, pip will only install stable versions as specified by PEP426 by default. If a version cannot be parsed as a compliant PEP426 version then it is assumed to be a pre-release.

If a Requirement specifier includes a pre-release or development version (e.g. >=0.0.dev0) then pip will allow pre-release and development versions for that requirement. This does not include the != flag.

The pip install command also supports a –pre flag that will enable installing pre-releases and development releases.

所以尚不清楚Buildout的作者是否又在造轮子。
然后演讲的是python的玄机,除了deep
copy经常遇到。其他的好像都没有碰到过…要么是自己太遵循c/java的规范了,要么是用python的编程年头还太少。

之后上午的演讲就全部结束了。结束后去自动化所吃了个非常难吃的饭。我头一次把各种肉剩下,而只吃了米饭。

下午演讲继续。去哪儿运维人员用python+openstack + flask+postgreSQL+extJS+nginx做了一个后台的运维系统。主要是为
了加入新的设备,程序的部署和发布。这个系统看起来相当不错。最主要的是这个系统把整个发布流程简化,只需要运维人员审批同意,就可以自动上线。管理机器也很方便。我
觉得运维到那家公司应该很轻松。我想如果把整个文档需求设计=>开发=>代码评审=>测试环境部署=>测试=>线上部署/发布
流程整个都自动化的去做。这个公司的人都会很轻松。

下面是python + Hadoop,对于Hadoop的优化,讲到主要看三个东西:tcp_mem, socket stat,
tcp_max_orphans。另外他们把整个计算模式分为三个:pig, cascading, streaming.其中pig就是使用SQL-
like的语言来执行Map-reducing操作的。cascading就是Map-Reduce操作JAVA库和API。这里应该是某些难于用SQL-
like语句写的,就用编程的方式实现。而streaming是使用单独的脚步和命令作为Map-
Reduce的输入的,所以这里应该是对于需要快速实现,而且又很少需要维护的应用建立起来的(例如运营人员的特殊要求)。

另外也介绍一下Seafile这个开源项目。该项目是企业云存储云盘,为文档为中心的协作平台。可以聊天可以写wiki等。但是我唯一感觉到的不足之处在于文件存储于
S3/swift/ceph之上。觉得如果存储在server搭建的本地磁盘或者SAN/NAS上更易于现在的企业使用。另外还有一个问题,seafile的所有数据
都是加密的形式传入到server。我觉得至少分享给团队的文件不应该使用个人的密钥进行加密,或者在企业内部分享的文档不应该加密。一旦某人离职,他的文档就要被其
他人员接手,否则那份文档就变得不可控了。这样做很安全,但是很不方便。另外Seafile将用户的密钥加密中间密钥,然后使用中间密钥对各个文档进行加密,用户自己
的密钥是不传给服务器的,这样解决了改密码,所有文件需要重新加密的问题。这里仍然有些问题。一旦用户忘记密码,他的所有文件都拿不回来了。另外一个问题,如果用户删
除了自己的本地数据包括中间密钥。他如何重新生成中间密钥。如果使用用户id+特殊字符做的可重复生成的中间密钥,那么一旦算法公布,这个很容易被破解。如果是随机密
钥或者不可以重复生成的密钥,那就意味着数据一定会丢失,或者有运维人员介入。所以细细想想都不是可靠的协议/算法。
在这个会里,除了有这些总结外。还有非技术的总结:

1.    ppt最好不要使用黑色背景。虽然这是脑残MAC粉的最爱。因为你们很喜爱,导致台下各位看不清,这样就不对了。

2.    如果脑残到一定要用黑色背景,请一定使用白色的文字(而不是其他颜色的文字)。

3.    演讲一定要兴趣点,如果开发出一款很像造轮子的应用/产品/框架,请简单的定义和比较你的应用/产品/框架(最好两三页PPT)。如果两三页PPT讲不完
,这说明这个东西太复杂了。如果还凑不齐一页,那就忽略区别吧。用实际的例子说明更好。枯燥的文字,看得大家都很想睡觉。

4.    如果ppt中copy了命令行、或者把命令行截图了。那么背景也不要是黑色。如果是,请把字体都变成白色。

5.    演讲中尽量用图而不是用文字。

6.    演讲一定要对着台下的听众。如果你觉得背对着听众演讲更为出色的话,也请不要挡住ppt的大屏幕。
7.    我个人觉得手工上去敲代码,给听众演示。还不如做一个视频。做视频更为繁琐。而现场敲更容易出错。

今天公司断网,身为一个互联网公司,断了一天的网。身为一个让网络变得更容易的公司,断了一天的网…这让我重新审视了所处的环境。

不过还是利用断网时间,详细看了一下 这个SQRL技术

下面简单的介绍一下该技术:

1. 主要使用物件作为认证的方式。现在它给的一系列技术,主要是QR(二维码)。其实最主要也就是二维码中的一个经常变化且在一定时间内唯一的Nonce串。另外我个人觉得作为扩展,现在的动态口令卡,各个银行发的电子令牌都可以归为这一类。不过SQRL使用的可以开源免费的算法,而且可以安装在任意电子设备中。所以这个是一个通用技术。比银行发的电子令牌这些更加通用。

2. 只是了消除username/password这个东西。其他的安全问题基本上没有考虑。不过这个消除不知道到底是一种进步,还是一种退步。因为它仍旧使用了另外一套复杂的密钥(公钥私钥)代替了现有的username/password简单密钥。

3. 仍然不能针对“人”这个实体做认证。偷走了手机,偷走了其他认证设备就意味着别人可以做为“你”登陆。

4. 算法详情:
每个人都有一个唯一的256bit的ID作为自己的唯一ID。这个ID再所有可以进行SQRL认证的网站上都是统一的。另外自己的设备中有一个MasterKey(主钥)。这个Master Key可以用于生成各个网站的private-public key.
4. 1 首先设备读取到了信息(扫描二维码/获取到认证需要的url) 例如:https://chillyc.info/login?sdaf9203rfnlnvq0239sdf,其中sdaf9203rfnlnvq0239sdf这个是随机生成的并且在一定时间内是唯一的。
4. 2 然后设备使用Master key 对 domain(chillyc.info) 进行HMAC 散列生成 只对 chillyc.info 有作用的private-public keys(其实这一步HMAC完全不需要,只需要生成一个不重复的private-public key然后再做一个site到private-public key的Map就好了。)

4. 3 使用 private key对https://chillyc.info/login?sdaf9203rfnlnvq0239sdf进行加密,然后发送 POST https://chillyc.info/sqrl  并且带上public=[your publickey]和sign=[刚才用private key加密的串]

4. 4 网站使用设备给的公钥对加密串进行解密。然后就取到了你的ID。这中间不需要任何 username/password. 现在讨论一下上面的算法是安全的吗?

1.  整个过程使用了Https协议,可以有效的防止中间人攻击(如果客户端检查服务器证书的话)。

2.  使用POST 发送数据,防止了某个中间代理截获url parameter,或者写入到log中。 例如网站使用的nginx,https证书也放在nginx上。然后…log中就出现了不该出现的东西。
3.  因为对每个网站(domain)都有不同的private-public key, 这样就将安全问题隔离在某个domain中,不会引起扩散。

4.  OS 需要保护好 Master Key。如果使用我建议的做一个site到private-public key的Map,那么Master Key就不重要了。但是这需要保护的就是整个Map. 看起来只保护Master Key可能更简单一些。

5.  另外SQRL 还希望,用户在扫描到二维码时,需要给用户展示一下domain,并且提示一下用户,是否对该domain进行授权。这样就防止了恶意的domain,或者假冒的domain。从算法协议层面,看起这个东西是安全的。可是仍有下面的一些不足:那个链接中有提及,我就直接照抄了。

  • How are identities backed up and/or cloned to other devices?
  • What about logging into a website displayed on the smartphone’s own browser?
  • What if the smartphone that contains my identity is lost or stolen?
  • What about password protecting logins on the phone?
  • What if the phone is hacked?
  • What about different people (and identities) sharing one phone?
  • What about having multiple identities for the same website?

对于第一个问题,我想解决的方案非常简单。可以对在手机生成一个二维码,用另外一个手机拍照,这样就能够获取到Master Key. 然后就能方便的生成site-public-private key.

对于第二个问题,其实完全可以使用JS和native app通讯。或者 browser中带有SQRL client。

对于第三个问题,这必须有一个SQRL的中心节点,该节点可以控制对已分配和已授权的Master Key进行回收和清除。不过这个问题又来了,该中心节点如何认证用户呢?看起来SQRL 这个东西不是完备的。

其他的问题我觉得…都很难解决。有些并不是SQRL这个算法协议所能解决的。

总结:
SO, 这东西看起来很美,但是它只不过用一套更加复杂的密钥机制代替了人工可记忆的密钥。让我想到:技术宅永远都不可能改变世界。他们只不过给世界一个美好简单的假象。当你仔细观察假象的时候,却发现竟然是由复杂丑陋的拼图拼接而成。该死的技术宅们…请研究一套能够真正能够认证“人”的技术。即使记忆可以移植,即使DNA可以复制,即使指纹可以盗用,即使所有的一切都可以复制。但是只有一样只会随着人本身发生变化,那就是每一个人在其生活环境、经历、岁月中造就的思维模式。你可以copy 我的文字,但是你写这个主题,不可能和我一字不差。不过这种变化的认证估计在我能看到的未来都不可能实现…

我每天做的事情都要考虑反垃圾的问题。这个问题实在让开发者和产品设计者头痛。产品的初衷是易于使用。结果因为该死的一小撮人,产品必须设计的难以使用。举个例子:我做了方便用户注册的产品。结果就被注册机利用了。举另外一个例子:做了一个方便用户发邮件的功能,结果就被垃圾邮件利用了。所以如果没有了这一小撮人,这软件设计和使用起来真的是很方便。

不过人心是险恶的。所以必须小心堤防。所以才要反复的检测软件,是否留有漏洞,小心的填补每一个漏洞方为上策。这也是做安全的为什么会火。做桌面安全的为啥会火的原因。嗯,人心是险恶的。

古人云:德者本也,财者末也。为什么古人要写这句话呢?因为古人所在的时空和今人所处的时空都是一样一样的。这和草履虫有趋光性是一样一样的。

好吧,希望下次写出的服务没有被沦为垃圾的发源地,希望下次的服务没有XSS,希望下次的服务没有CSRF。

关于安全问题,又想起来不久前给老爸申诉QQ号的事情。在一遍一遍外部申诉凑齐7龙珠一般凑齐7个人后,结果被QQ运营一次次驳回申诉。每次的原因都一样:请更多的人帮忙申诉…我总感觉QQ运营是和盗号的是一伙的。后来彻底对QQ外部运营失望了,走了腾讯内部申诉,很快就申诉成功了。当安全信息沦为盗号者的工具时,我觉得还是人更靠谱些。嗯不,是熟人更靠谱些。

这个问题的解决方法是:

1
2
3
4
5
6
7
8
9
10

停掉resin

在conf/resin.xml中找到
<web-app id="/share" root-directory="webapps/share"/>
这样的节点

然后删除节点,再删除webapps中对应的文件夹。

重启 resin

导致这个文件夹,在重启之后,会自动复原的原因可能是:

1
2
3

1. 启动 resin后,resin会将 war包解压,并将解压路径记录到resin.xml中。
2. 一旦重启。resin的watchdog会查看 resin.xml然后根据 <web-app>节点从临时文件夹中将webapp复原。

新的一周开始,周一的工作结束。

写了一堆testcase, 还是没有习惯先写testcase后写代码。主要原因在于Java编译器各种错误,实在讨厌。

现在逐渐开始先写文档,后写接口,再写实现。发现这样效果不错。

testcase中主要流程走通,然后测几个特定异常,心理上基本就认为到了可以提交的状态。

今天在想,写的模块和之前的写的差不多。什么时候机器能替我思考一下,这样的代码能变成模版?

今天面临分页和全部数据的取得。其实用户不会取所有的数据。希望获取所有的数据,基本上就是客户端喜欢偷懒。但是一定要坚持对所有可能获取多个值的东西做分页,虽然有时候自己也觉得这样做太荒谬。后来想想还是得这么做。因为客户端发布出去,除非强制升级,否则没有任何办法做到兼容和数据量上的扩展。真心觉得所有的客户端都应该强制升级来保证服务的一致性。否则server端真是太难维护了。

我觉得公司,特别是总公司做事情应该有规划,做什么大的模块一定要统一。否则后期再做数据的统一,一是耽误时间,二是有可能数据会有丢失或者出错,三是有可能停服。现在想想,打通账号真是的非常繁琐,非常耗时,以及非常没有啥技术含量的事情。

话说回来,single sign on 这鬼东西做了这么多年,最终还是没有出来一个统一的解决方案来支持账号的整合。那些work group到底是干什么吃的呀…

很多时候需要从spring中取出相应的对象,很多时候又需要将对象注入到spring context中。

小卒碰到了这样的问题。在构件中有DAO. 其他构件需要复用这个DAO,但是连接配置各不相同,并且也有开发测试环境,milestone环境和线上环境的不同分别。需要这个DAO能够获取到其他模块/工程/构件的配置。下面不说废话了,直接上代码:

首先是DAO.java 类。这个类用于load xml等信息构建出context.

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

private static volatile DAO instance = null;

public static DAO getInstance() {
if (instance == null) {
synchronized (DAO.class) {
if (instance == null) {
loadConfiguration();
instance = new DAO();
}
}
}
return instance;
}

private DAO() {

}

private static String configBeanXML = null;
private static Class<?> loadClass = null;

public synchronized static void setConfigBean(String xmlfilePath, Class<?> loadClazz) {
configBeanXML = xmlfilePath;
loadClass = loadClazz;
}

private static ApplicationContext ctx;

public static ApplicationContext getCtx() {
return ctx;
}

private static void loadConfiguration() {
if (configBeanXML != null && loadClass != null) {
LOG.info("load ddb configuration.");
ApplicationContext parent = new ClassPathXmlApplicationContext(new String[]{"raw.xml"}, loadClass);

BeanDefinitionRegistry reg = (BeanDefinitionRegistry)parent.getAutowireCapableBeanFactory();
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(BoneCPDataSource.class);
builder.addPropertyValue("driverClass", "your drive class");


builder.addPropertyValue("jdbcUrl", "your url");

builder.addPropertyValue("username", "username");
builder.addPropertyValue("password", "password");

builder.setDestroyMethodName("close");
reg.registerBeanDefinition("dataSource", builder.getBeanDefinition());
ctx = new ClassPathXmlApplicationContext(new String[]{configBeanXML}, loadClass, parent);
} else {
System.out.println("database configuration can not be load.");
}

}

public JdbcTemplate getJDBCTemplate() {
DataSource datasource = (DataSource) getCtx().getBean("dataSource");
return new JdbcTemplate(datasource);
}

}

这里的逻辑就是首先构造一个空的applicationContext.然后实例化DataSource后,将dataSource注册到applicationCo
ntext中。然后再用这个applicationContext作为父Context构造真正的ApplicationContext.

那个raw.xml就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">

<!-- keep it empty -->

</beans>

真正用于初始化各个transaction类的xml如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">

<!-- this is the service object that we want to make transactional -->
<bean id="transA" class="chillyc.info.dao.TransactionA" />



<!-- similarly, don't forget the PlatformTransactionManager -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

<!-- the transactional advice (i.e. what 'happens'; see the <aop:advisor/>
bean below) -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- the transactional semantics... -->
<tx:attributes>
<!-- all methods starting with 'get' are read-only -->
<tx:method name="get*" read-only="true" />
<!-- other methods use the default transaction settings (see below) -->
<tx:method name="*" rollback-for="java.lang.Exception" />
</tx:attributes>
</tx:advice>

<!-- ensure that the above transactional advice runs for any execution of
an operation defined by the Transcation bean interface -->
<aop:config>
<aop:pointcut id="transcationServiceOperation"
expression="execution(* chillyc.info.dao.transactions.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="transcationServiceOperation" />
</aop:config>

<context:component-scan base-package="chillyc.info.dao" />
<!-- define the SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="typeAliasesPackage" value="chillyc.info.dao.orm.data" />
</bean>

<!-- scan for mappers and let them be autowired -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="chillyc.info.dao.orm.mapper" />
</bean>


<!-- other <bean/> definitions here -->

</beans>

这样之后,在初始化database-connection.xml时,各个bean就能获取到dataSource这个对象的引用了。

一. 上海

1.  到了上海,住姐姐家,省了不少食宿费,感谢姐姐姐夫。下午去了步行街,然后走到外滩,看了东方明珠。顺便看了情深深雨蒙蒙跳桥的外白渡桥,然后做了两元的渡船到了浦东,下渡船后可去陆家嘴坐地铁。
2.  第二天去了城隍庙,小吃真没什么好吃的。请京城来的土豪和湘川来的吃货绕道而行吧,不过金银珠宝店很多,可以逛逛,另外各种礼品店东西都很贵。
3.  从乌镇回来后,又去了上海的杜莎夫人蜡像馆。里面有几个人物相当逼真,但也有些人物怎么看都不对劲。150大洋感觉花得不怎么值,因为里面的蜡像没有几个。使劲拍照,半个小时也就看完了。

二. 苏州

1.  从上海出发去了苏州,去了拙政园。园林很精致,像一大盆盆景,处处能看出园主的用心。不过太多的精致却忘记了大道至简,大美至拙。看到最后反而感觉弄巧成拙了。从拙政园出来已经快下午五点,顺便去了一趟苏州博物馆。可能因为免费,排队的人非常多。我们运气好,只排了十五分钟。博物馆各种古时金银玉石青铜衣帛,看起来花纹很不错。一方面感慨当年工匠技艺高超,一方面又感觉像是假的。因为纹路太清晰了!看过大博物馆的就没必要在这里浪费时间了。晚上继续逛步行街,小吃仍旧没有什么特色。叫化鸡只卖10元,最便宜,但是没买不知味道如何。去了五方斋吃了顿,它的大菜做的味道不太好。在那里没点小吃,不过依然吃饱。又去了朱裕兴吃面。那面的确好吃但是面的配菜真不怎样,例如焖肉面,焖肉就是一大块大肥肉,肉也不好吃。

2.  去苏州的第二天去了虎丘看了剑池。虎丘最好看的景就是斜塔和斜塔后的万家灯火,其他都感觉是渣渣。看着看着天公降雨。从百度上看到有人说可以直接走去七里山塘街,结果接近走了3小时,又没车搭。风雨中落汤鸡的感觉真是好极了。该死的导航指向了死胡同,所以城市旅游一定要买城市地图。什么xx导航都不管用,还不如当地搓麻将的大妈。进了山塘街就见大戏台唱戏,风雨中声音更显凌厉。山塘街上五方斋还是一贯的便宜,吃了顿饭暖暖身子。

3.  在苏州看到的太湖珍珠倒是真的。不过圆润度不好,而且大小不一,觉得同一大小又圆又亮的真难找,珍珠项链贵就贵在这里,不过话说回来,人工养殖的河蚌也很便宜呀。在苏州看到的各种打银店感觉都像假的。因为一块银这样敲成镯子耗费的时间太长,而且做工也会很粗糙。银首饰灌注才是正途。so不要被表面所迷惑。话说这些天最好吃的就是kfc的甜筒,三元一个,魔都四元一个。另外苏州观前街有绝味鸭脖店,观前街附近还有哑巴生煎。特别推荐苏州的哑巴生煎,味道很好,比京城的生煎好吃太多,其他菜也都很便宜。

三. 杭州

1.  到了杭州住湖滨,很方便,中午在银泰绿茶吃了顿,点了四个大菜,竟然没有过百,太太太便宜了,差点撑死我。

杭州插曲——观钱塘江大潮

2.  吃完饭去了钱塘江边看潮,最好坐地铁到九堡,然后打车到大王庙路,下车问人在走3分钟就到钱塘边上,九堡很难打车,拼车最好。千万不要让司机找一个观潮点。这些出租车司机非常狡猾。如果不是我开着导航,他肯定又把我带回了西湖。土豪等有钱人可以去盐官观潮点或者老盐官。这两处在海宁(不在杭州),特别是老盐官,运气特别好的人会被请到龙宫,天天看潮。

3.  晚上去了南宋御街的步行街(这几天看这种老街真是看到吐),千万不要去附近卖西湖醋鱼便宜的餐馆。我不知道西湖醋鱼是否就这味道,反正我是觉得非常非常的难吃。只吃了一口就再也没有动那个鱼,我觉得自己基本上什么菜都能吃第二口,但在老杭州吃的西湖醋鱼彻底改变了我对菜的期望,这才是真正的重口味菜。

4.  在杭州的第二天去了西湖,周围都没有卖早餐的。饿到早上十点才找到知味观这家,梅菜饼和乌饭糕真好吃,其他吸取了西湖醋鱼的教训没敢尝鲜。

5.  一定要在西湖外面买矿泉水,里面真是太贵了。真不行就在西泠印社那里买,那里三元。另外那个印社的牌坊很有特点,很像日本的鸟社。西湖十景真玩不过来,没处景都有纪念币卖,感觉集齐10个就可以召唤神龙。淘宝一定有卖,每个三十。白堤苏堤一定要逛,从白堤逛到苏堤结束,可以坐浏览车重回断桥;或者坐船到看三潭映月,然后再回到湖滨。晚上可以看音乐喷泉,每半小时一次,挺壮观的。不行还是太累了,租个自行车游应该轻松很多。话说西湖风景真的很不错:视野开阔污染小,风和日丽。

6.  回来去了外婆家,下午四点排了长队,很像帝都五道口枣糕王。饭菜不错,东坡肉很好吃~有机花菜也不错,主要是饭菜非常便宜,下次来杭州准备点茶香鸡,另外也想去炉鱼那店看看。

7.  灵隐飞来烧香拜佛的好地方,但是自然风景不怎么样,进山的票+灵隐寺共75了,划不来。从杭州可以坐地铁到客运中心在那里坐长途汽车去乌镇。车程1个半小时,半小时一趟车。

四. 乌镇

1.  乌镇在淘宝上订民宿,一定要说好住哪里。编号60+的民宿都挺偏的。不过如果你喜欢偏静,倒是可以住那里。非临水房一般300大洋。另外要贪便宜可以去住紫藤公寓,那个不是民宿,住那里也不能乘免费的游览车。如果行程就两天,坐浏览车的次数也不多,一天也就是两三次,每次5元。还是觉得没必要住景区内,除非特别热爱小镇水乡。

2.  民宿早餐很丰盛,某些民宿有特色菜,不过没有吃到,最好吃的是两处卖梅菜扣肉烧饼的,物有所值,一定要买来吃。

3.  乌镇东栅真没有什么好玩的,都是卖东西的。乌镇西栅早上早起基本没人,拍照非常好。乌镇的阴晴雾雨算是都被我碰到了。从乌镇东栅到火车站只有一公里左右,可以步行。从东栅到西栅有免费的班车,半小时一班。西栅到东栅也有免费班车,西栅住宿可以办理出入证。另外西栅还可以坐渡船逛逛。
4.  从乌镇可以坐长途汽车回到上海虹桥或者上海南,车程一个半小时。

五.总结

到景点如果想省钱就不要买东西,淘宝上什么都有,想心情舒畅就买东西,想生气就买了东西再去淘宝上查。其实只有立刻吃到的才是值得买的。各大景点如果不想看人的话就直接网上看看图片即可,想看人的话也没必要去各大景点,直接来帝都挤挤地铁就好了,2块钱,心旷神怡。

0%