mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
786
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eac68166d9 | ||
|
|
cb0023345f | ||
|
|
f0f7feb125 | ||
|
|
c8e32253a5 | ||
|
|
01541bb208 | ||
|
|
2d44d06f36 | ||
|
|
fda930a162 | ||
|
|
5a6cdb9a86 | ||
|
|
5ef7fe8504 | ||
|
|
11586fb635 | ||
|
|
d555122067 | ||
|
|
b6d3dc3516 | ||
|
|
bbf54d6196 | ||
|
|
74c88ce511 | ||
|
|
309bf6a11a | ||
|
|
02b6ab0f74 | ||
|
|
0c41993ce7 | ||
|
|
d949c48245 | ||
|
|
93e603ca25 | ||
|
|
117d22fd67 | ||
|
|
f13de2c8a6 | ||
|
|
5b8ef8154a | ||
|
|
df892f4509 | ||
|
|
2755e6d1ec | ||
|
|
2024193da9 | ||
|
|
72788c7428 | ||
|
|
8789709fb8 | ||
|
|
588aaf9361 | ||
|
|
68649d0e98 | ||
|
|
f6047d3b73 | ||
|
|
629e096e28 | ||
|
|
c981944c8e | ||
|
|
1ba9f4e9de | ||
|
|
ce02e7ce70 | ||
|
|
1d9a2ed43a | ||
|
|
ae81ce5f47 | ||
|
|
9e42f98902 | ||
|
|
ada21729ec | ||
|
|
8b7d882b8a | ||
|
|
cf2d9df256 | ||
|
|
48b05eb358 | ||
|
|
0796fc0d88 | ||
|
|
df585644e9 | ||
|
|
ff3c5b3d82 | ||
|
|
1a03e84f0e | ||
|
|
49ab17cd34 | ||
|
|
9cd189034c | ||
|
|
ed566ca153 | ||
|
|
9159cec36c | ||
|
|
b58fc2df04 | ||
|
|
dd8607b072 | ||
|
|
a5a8765bd0 | ||
|
|
ae55b58438 | ||
|
|
671d082ad3 | ||
|
|
43e95d81f6 | ||
|
|
3a64cf6952 | ||
|
|
8d976f83a9 | ||
|
|
117737c790 | ||
|
|
085843339f | ||
|
|
bf33b3f96f | ||
|
|
e1d4159453 | ||
|
|
4f1f3cadf9 | ||
|
|
01e1a4642b | ||
|
|
3a75e1f66c | ||
|
|
47ac4c5c5b | ||
|
|
9c73309dfd | ||
|
|
bd3c99e67f | ||
|
|
5adbebaf6e | ||
|
|
6a9ab49ef0 | ||
|
|
4308a98a0a | ||
|
|
f86f1b2647 | ||
|
|
96cc8d7605 | ||
|
|
59537227b6 | ||
|
|
81c6d4601d | ||
|
|
e21839de91 | ||
|
|
a61db2ab35 | ||
|
|
bddfa3bc5f | ||
|
|
9ad460ecd8 | ||
|
|
50593a7f9d | ||
|
|
c088cfa339 | ||
|
|
e683e25a95 | ||
|
|
c7052c5bfa | ||
|
|
698ee2198d | ||
|
|
91c4eca528 | ||
|
|
81897bd54d | ||
|
|
2427630c07 | ||
|
|
3238c0def4 | ||
|
|
93f75151f0 | ||
|
|
32e9b48359 | ||
|
|
258e29d2f0 | ||
|
|
ec2f1f7243 | ||
|
|
5775985468 | ||
|
|
4788dd4ec9 | ||
|
|
ca7e5b79b8 | ||
|
|
90dfbcd7b3 | ||
|
|
6b39bad4da | ||
|
|
25dea0b5b0 | ||
|
|
0069d2fabc | ||
|
|
98964638ba | ||
|
|
4573a995f1 | ||
|
|
4bc80cc724 | ||
|
|
a2972abe3e | ||
|
|
54eaf561f4 | ||
|
|
3134dd3165 | ||
|
|
8fc6abd480 | ||
|
|
f031bb72b6 | ||
|
|
42f0b419e8 | ||
|
|
7cbae758fc | ||
|
|
fb32cfa170 | ||
|
|
ffa8268125 | ||
|
|
eba1003c19 | ||
|
|
eca6383dba | ||
|
|
624d08f46c | ||
|
|
14749daf9b | ||
|
|
2817875706 | ||
|
|
3f8d705439 | ||
|
|
e9faef2692 | ||
|
|
d459807af7 | ||
|
|
10ad9ef096 | ||
|
|
11e3649b85 | ||
|
|
ddc00fedb6 | ||
|
|
0aa9208dcc | ||
|
|
e3b1996dc9 | ||
|
|
24ca744e06 | ||
|
|
7f9ce5bdd4 | ||
|
|
23e5de0819 | ||
|
|
c23508819f | ||
|
|
af030e3da4 | ||
|
|
d8e67d09a9 | ||
|
|
66c08e663e | ||
|
|
9a096e272e | ||
|
|
21e87e12cb | ||
|
|
7c6d7dd4d0 | ||
|
|
c5329e9db0 | ||
|
|
bccb9f8ddb | ||
|
|
c57af29fca | ||
|
|
37b6a321ab | ||
|
|
896f7d9ea8 | ||
|
|
141a0d9358 | ||
|
|
5a7f24da5d | ||
|
|
8ea86d6c6d | ||
|
|
fb5eeb60b1 | ||
|
|
33ec8abb9d | ||
|
|
bbd7baf1be | ||
|
|
2e36402a76 | ||
|
|
fa4af4ef98 | ||
|
|
ed6a9e2dbb | ||
|
|
42d83705b7 | ||
|
|
ae58d9223a | ||
|
|
a9a89938f9 | ||
|
|
4b91f5fe0b | ||
|
|
85d3caf079 | ||
|
|
f6d8c8b618 | ||
|
|
d0fa3f0cb1 | ||
|
|
41a31c1aa0 | ||
|
|
ab301f970c | ||
|
|
d1b7b082a2 | ||
|
|
5fb3a1e019 | ||
|
|
cb0dee998b | ||
|
|
53b606289d | ||
|
|
574d5e215b | ||
|
|
508f2129f5 | ||
|
|
cdfcbeec43 | ||
|
|
0e9782e6b3 | ||
|
|
927fa5f7d3 | ||
|
|
53a80c7dd1 | ||
|
|
89789f3347 | ||
|
|
9dc5ca602c | ||
|
|
95737cd3c5 | ||
|
|
0a49fc9b62 | ||
|
|
f8c80c7fd2 | ||
|
|
fc837b6472 | ||
|
|
cfaedcdbc5 | ||
|
|
111bac7514 | ||
|
|
1a2ca63d65 | ||
|
|
33031d1c69 | ||
|
|
49d9d88fbe | ||
|
|
756f867ed2 | ||
|
|
12108ada38 | ||
|
|
fb2fdc9b53 | ||
|
|
d570971fd1 | ||
|
|
a8434f22ff | ||
|
|
b0d3921264 | ||
|
|
4167dfc4af | ||
|
|
e49a0c9ffb | ||
|
|
dd6f223ae1 | ||
|
|
05aff53241 | ||
|
|
9a3362e842 | ||
|
|
04e0069eb3 | ||
|
|
b8c4e1c3d3 | ||
|
|
6b136248fa | ||
|
|
aa004f11af | ||
|
|
acd5578d8a | ||
|
|
07946cd505 | ||
|
|
57870aaa05 | ||
|
|
cca837bcfa | ||
|
|
534eb2f0e2 | ||
|
|
dfa7b765f9 | ||
|
|
4ef581f87c | ||
|
|
eb96c726fd | ||
|
|
abbcd35ee6 | ||
|
|
db5a2b194d | ||
|
|
74e0a16531 | ||
|
|
8601f51a6c | ||
|
|
6534feb5da | ||
|
|
6cf5e3386d | ||
|
|
7651256083 | ||
|
|
057d7480b0 | ||
|
|
85c4385498 | ||
|
|
74a9019535 | ||
|
|
c44931ea25 | ||
|
|
012322503f | ||
|
|
5575273c96 | ||
|
|
f3251691e2 | ||
|
|
9d336cb20b | ||
|
|
9895e48485 | ||
|
|
4799c56e25 | ||
|
|
602a35d0b6 | ||
|
|
4bc1f2ede4 | ||
|
|
674617eb9e | ||
|
|
afead2da41 | ||
|
|
607cf3156c | ||
|
|
af2b8976fa | ||
|
|
85bfe3d03e | ||
|
|
eda6350d89 | ||
|
|
8ac1b745df | ||
|
|
6c8cf0c5b7 | ||
|
|
d0b24dc340 | ||
|
|
2042bdf29a | ||
|
|
4f327c2751 | ||
|
|
fbc2ba3912 | ||
|
|
93d659050b | ||
|
|
2520837f5e | ||
|
|
2f349ff054 | ||
|
|
0aec597cf2 | ||
|
|
5faec61cd2 | ||
|
|
7252e2d7d9 | ||
|
|
0cc87009d0 | ||
|
|
ef58954af5 | ||
|
|
824f5e468e | ||
|
|
d84cdab714 | ||
|
|
5a3550e603 | ||
|
|
f840cfc2ae | ||
|
|
8e0d2f8749 | ||
|
|
eef514f922 | ||
|
|
4fc7898f85 | ||
|
|
6258a5d263 | ||
|
|
dbf5f3b57b | ||
|
|
4b594fb533 | ||
|
|
c0efd49c73 | ||
|
|
54ba1480eb | ||
|
|
4d8bc0a2ce | ||
|
|
e182190f96 | ||
|
|
8711ebc6f7 | ||
|
|
baa7242939 | ||
|
|
47e8d27212 | ||
|
|
70aed3906e | ||
|
|
70c3860a65 | ||
|
|
1ba21d5c43 | ||
|
|
2019bc7bf1 | ||
|
|
3cb52917e3 | ||
|
|
55a257a64c | ||
|
|
83ef204f3c | ||
|
|
a18cac035b | ||
|
|
873c14faaf | ||
|
|
e1af7c29eb | ||
|
|
c1c85b67c2 | ||
|
|
05b8adbf6e | ||
|
|
3ca4efc818 | ||
|
|
ec2c3b13d4 | ||
|
|
5a3e386b7c | ||
|
|
7e56a147be | ||
|
|
62cdfb3209 | ||
|
|
740532da4b | ||
|
|
130356cb3b | ||
|
|
ab84cfae52 | ||
|
|
ad21b7a3fd | ||
|
|
31581082d2 | ||
|
|
aa84dfe7d8 | ||
|
|
aad5b33db2 | ||
|
|
5801f7620d | ||
|
|
514416a1e0 | ||
|
|
8faa95adc1 | ||
|
|
ebaeafc11b | ||
|
|
20e46cad9b | ||
|
|
98e014f8c8 | ||
|
|
78912d75e0 | ||
|
|
09786a7062 | ||
|
|
54530248a5 | ||
|
|
9953ef35eb | ||
|
|
fdafa776e1 | ||
|
|
392eb2744d | ||
|
|
b642b3c644 | ||
|
|
0e1c815826 | ||
|
|
83b463040f | ||
|
|
aea7d937eb | ||
|
|
78628ffad0 | ||
|
|
56a742ad70 | ||
|
|
a4415b03ff | ||
|
|
e252f71792 | ||
|
|
18ca3403ac | ||
|
|
94a118d635 | ||
|
|
efe325034b | ||
|
|
0c368ed7b3 | ||
|
|
c1b022c123 | ||
|
|
021d57f4b2 | ||
|
|
11e180aa3f | ||
|
|
9e2ddc9f8c | ||
|
|
d4e72eefa0 | ||
|
|
525afbc00f | ||
|
|
6f2e1c0248 | ||
|
|
0d0a3422fc | ||
|
|
57b0190cd2 | ||
|
|
c8e199177c | ||
|
|
68a470a086 | ||
|
|
45a023f0e4 | ||
|
|
06bce35f5a | ||
|
|
3dfdd0e2e8 | ||
|
|
a569c7fc96 | ||
|
|
e751f991cb | ||
|
|
83cf768369 | ||
|
|
bb418456e3 | ||
|
|
ac9f3408b2 | ||
|
|
78f8d48e2e | ||
|
|
3f8b9529e1 | ||
|
|
10f3d85d9f | ||
|
|
0e3f47ee82 | ||
|
|
cdbbd749a3 | ||
|
|
a712be2917 | ||
|
|
0cba0664f8 | ||
|
|
a3e5c3a786 | ||
|
|
58def02cd1 | ||
|
|
cec65cc888 | ||
|
|
da7bb834e7 | ||
|
|
a64a244745 | ||
|
|
1c19d4cf7e | ||
|
|
347225efb2 | ||
|
|
91cb5a23fd | ||
|
|
0e219a9bc6 | ||
|
|
21182da793 | ||
|
|
0de084caec | ||
|
|
e3425a8850 | ||
|
|
8e9843ab8a | ||
|
|
75462e7410 | ||
|
|
265bda8cf2 | ||
|
|
7d0ed261b5 | ||
|
|
ff111eaf33 | ||
|
|
8b18e907af | ||
|
|
40704e2ef7 | ||
|
|
0e62d0579e | ||
|
|
47d55a2f29 | ||
|
|
b037be5d3e | ||
|
|
60a3c24cba | ||
|
|
4a243a76a7 | ||
|
|
9aee7c892d | ||
|
|
18d944f228 | ||
|
|
29bb9d3b48 | ||
|
|
f8ea5619d0 | ||
|
|
e3fac7c622 | ||
|
|
8ee68ac5e1 | ||
|
|
ec7b376612 | ||
|
|
f2d438f21a | ||
|
|
20bd2241e4 | ||
|
|
dd907cc4bc | ||
|
|
4d122ea05b | ||
|
|
83384c6f0e | ||
|
|
eaa48468cd | ||
|
|
0b1c3a4233 | ||
|
|
3e8b06e38e | ||
|
|
81d48d67a7 | ||
|
|
8d0df6541e | ||
|
|
2be3099d55 | ||
|
|
183d079b58 | ||
|
|
40634412f5 | ||
|
|
b8aa822a30 | ||
|
|
a1d0fbb194 | ||
|
|
70ccc6588d | ||
|
|
3d62e84d59 | ||
|
|
ac36172cc6 | ||
|
|
1829c74694 | ||
|
|
cc7e22014d | ||
|
|
a5c6f483b0 | ||
|
|
5237e8ba66 | ||
|
|
9926cad7f1 | ||
|
|
6c1b295d41 | ||
|
|
49b568e7d4 | ||
|
|
628af31ddd | ||
|
|
31cac60393 | ||
|
|
6a067c0cf7 | ||
|
|
842292ef6b | ||
|
|
6db9a60f4f | ||
|
|
d9125db957 | ||
|
|
4bd9e67703 | ||
|
|
2a986ab590 | ||
|
|
7c477827fe | ||
|
|
b2967dec1e | ||
|
|
a95fd063ae | ||
|
|
17a54e3804 | ||
|
|
f1b2a09b30 | ||
|
|
e4d9e4f2de | ||
|
|
6474f306b5 | ||
|
|
f1c2f134ad | ||
|
|
0f7158ea18 | ||
|
|
3fdb76d18f | ||
|
|
b1027a2f25 | ||
|
|
e5a1f9f7cc | ||
|
|
f78f24136a | ||
|
|
5b2aa0c74d | ||
|
|
175714046a | ||
|
|
73b2cc55f3 | ||
|
|
cebe375856 | ||
|
|
2f47235018 | ||
|
|
17abd6d977 | ||
|
|
cc5f3689f3 | ||
|
|
abcc651e7f | ||
|
|
caa36262c1 | ||
|
|
66b6dba921 | ||
|
|
fc01f230f3 | ||
|
|
7fe3e657d7 | ||
|
|
9bade12c9d | ||
|
|
7a816ae2da | ||
|
|
b83412281e | ||
|
|
478bba292d | ||
|
|
77a4e684bb | ||
|
|
a8254c4295 | ||
|
|
2a5d0084ef | ||
|
|
f9404639d7 | ||
|
|
bbef5a6bbb | ||
|
|
4645fd9725 | ||
|
|
823cb45221 | ||
|
|
b2bbb05b83 | ||
|
|
5ff71324d0 | ||
|
|
851f1c98b0 | ||
|
|
a92e620f7e | ||
|
|
f9cd955f7a | ||
|
|
71f66005e7 | ||
|
|
b682293a32 | ||
|
|
5bd7853ce8 | ||
|
|
276c56161a | ||
|
|
6b01821623 | ||
|
|
8495ccced6 | ||
|
|
2d929837c4 | ||
|
|
6754992b1c | ||
|
|
08b7f4a95f | ||
|
|
07e2605a30 | ||
|
|
9a0b742f14 | ||
|
|
bce745e88d | ||
|
|
51d2e4c600 | ||
|
|
4e33027fdf | ||
|
|
0a809b0fb3 | ||
|
|
13ddb915c8 | ||
|
|
07cdf64ece | ||
|
|
b9ae76d465 | ||
|
|
82f5c21d26 | ||
|
|
14105eb1eb | ||
|
|
bab27f25ca | ||
|
|
eed8d69715 | ||
|
|
7b93df2df7 | ||
|
|
bb205ced54 | ||
|
|
0d4c50e19c | ||
|
|
8cff85fdda | ||
|
|
932986a6b5 | ||
|
|
01539091ef | ||
|
|
7e69f71b51 | ||
|
|
58d6b2c24e | ||
|
|
3c7ef7d46e | ||
|
|
db5db26fca | ||
|
|
7110f4a59b | ||
|
|
9f91d8bcf9 | ||
|
|
ac97444ad1 | ||
|
|
f6e9afcab6 | ||
|
|
a7226079fe | ||
|
|
fc915010d9 | ||
|
|
c1818bc309 | ||
|
|
4a4134ee93 | ||
|
|
e07bd8f736 | ||
|
|
4d0b29624e | ||
|
|
c7f4b56dd1 | ||
|
|
69c045443c | ||
|
|
fd2d083971 | ||
|
|
b9380aa5b8 | ||
|
|
3a1368a342 | ||
|
|
0e9fc31531 | ||
|
|
5132107748 | ||
|
|
7ff3d703f7 | ||
|
|
ef91c643ed | ||
|
|
f0ddf6c7e6 | ||
|
|
fcff571664 | ||
|
|
d5555ed9a9 | ||
|
|
c5a11aa5a8 | ||
|
|
d1099c8309 | ||
|
|
7ff8758797 | ||
|
|
f1ebe7929b | ||
|
|
0056568bc7 | ||
|
|
4b72924529 | ||
|
|
a75ed676f8 | ||
|
|
c3438b888d | ||
|
|
56be274acc | ||
|
|
84bb4ff78c | ||
|
|
4054eda1f6 | ||
|
|
a4b9354e43 | ||
|
|
06e033f23f | ||
|
|
156f505394 | ||
|
|
9b2b2f7787 | ||
|
|
e68dae0baa | ||
|
|
2cf864954f | ||
|
|
2b94487573 | ||
|
|
fcf730ec39 | ||
|
|
7d343455ac | ||
|
|
21e0b748a6 | ||
|
|
2fab733e4e | ||
|
|
7260a1b6eb | ||
|
|
434a005366 | ||
|
|
ff67ef60b0 | ||
|
|
0a1747d8e3 | ||
|
|
f36499f71f | ||
|
|
da818f17c2 | ||
|
|
10eb2bc21f | ||
|
|
66fe278e3f | ||
|
|
10607385e0 | ||
|
|
db6562df76 | ||
|
|
c0ec87d4cd | ||
|
|
0dbba7a211 | ||
|
|
e75670f207 | ||
|
|
fb8db14f3a | ||
|
|
c00b1849ef | ||
|
|
1e1c518641 | ||
|
|
d5b42f9ba1 | ||
|
|
bda564d2c8 | ||
|
|
dce124cabd | ||
|
|
f5cfcbd56e | ||
|
|
5bccdccbd6 | ||
|
|
6e62b72fb9 | ||
|
|
89dac3820b | ||
|
|
ea836a0078 | ||
|
|
1ffd7c7720 | ||
|
|
387582543c | ||
|
|
a215878a7c | ||
|
|
dbedb65922 | ||
|
|
1d52b3a818 | ||
|
|
6ce2b91fff | ||
|
|
55d66ae851 | ||
|
|
37d001f754 | ||
|
|
c121618c0c | ||
|
|
7ebef51859 | ||
|
|
093f82a87f | ||
|
|
b60f1f978f | ||
|
|
6ac146cabe | ||
|
|
2263d31f46 | ||
|
|
4a66d0be6b | ||
|
|
e68e496207 | ||
|
|
088110385f | ||
|
|
a355f66561 | ||
|
|
00fdb632d7 | ||
|
|
a54c4157e6 | ||
|
|
a89a9f0202 | ||
|
|
331f49824f | ||
|
|
94a857d345 | ||
|
|
1005f177fd | ||
|
|
96a4b93c75 | ||
|
|
5869187368 | ||
|
|
eac50b8f5b | ||
|
|
6e2712ea49 | ||
|
|
f6e5446dab | ||
|
|
4879d44da4 | ||
|
|
2c5bf98d0b | ||
|
|
45a571bd97 | ||
|
|
d0ca75c25b | ||
|
|
fb1c7667cc | ||
|
|
1909ba1557 | ||
|
|
e4eca4bbe9 | ||
|
|
b2438ecd71 | ||
|
|
ed08567d06 | ||
|
|
bbe8abcc2e | ||
|
|
1651d5e119 | ||
|
|
64bf6d6a35 | ||
|
|
47b0a917a2 | ||
|
|
2fe1a349c3 | ||
|
|
f168a8fd0d | ||
|
|
dda98c1dda | ||
|
|
88a13c5416 | ||
|
|
16b8f5da35 | ||
|
|
3471f6d206 | ||
|
|
0bf4c84bc8 | ||
|
|
d366336ee5 | ||
|
|
bf3f8ecff5 | ||
|
|
7524903aea | ||
|
|
90cf3fddc9 | ||
|
|
8e0f9f7591 | ||
|
|
24936c0a41 | ||
|
|
f02a552cdf | ||
|
|
5a5cb7ffbd | ||
|
|
f85fa772bf | ||
|
|
9f9ec8ef9f | ||
|
|
62707caf64 | ||
|
|
c6fc7a032d | ||
|
|
10faa1fae6 | ||
|
|
764f5ce4d4 | ||
|
|
65ba231fc0 | ||
|
|
1da2382e45 | ||
|
|
a86af94a8c | ||
|
|
8301cb634e | ||
|
|
721654d033 | ||
|
|
fe1817ef02 | ||
|
|
fc996659f2 | ||
|
|
368d22baa6 | ||
|
|
daf3d97266 | ||
|
|
f2179fca2d | ||
|
|
2c436c3af5 | ||
|
|
8d334ff2e8 | ||
|
|
f3e4696498 | ||
|
|
221260fa44 | ||
|
|
f8e7a79e9a | ||
|
|
b66f18bfd6 | ||
|
|
ac6708e0d0 | ||
|
|
a5f46c095d | ||
|
|
3ac7ea6e60 | ||
|
|
b2e0271ce7 | ||
|
|
c7a7651602 | ||
|
|
1e6678cbb4 | ||
|
|
a6e4e181bf | ||
|
|
b8e1dffca2 | ||
|
|
7e8ed72b7b | ||
|
|
ec8dd30a04 | ||
|
|
f81d5c9f3a | ||
|
|
74b24b1db1 | ||
|
|
179ef8c8a0 | ||
|
|
f77f3c3d6e | ||
|
|
9a7646195c | ||
|
|
84d3578e33 | ||
|
|
ef05b81014 | ||
|
|
726c682984 | ||
|
|
0c22a99fdf | ||
|
|
c062c8e001 | ||
|
|
06f3c000f5 | ||
|
|
8c0232fdb8 | ||
|
|
2470486f20 | ||
|
|
8fbf0374dd | ||
|
|
dd8fff0277 | ||
|
|
73b3d1d977 | ||
|
|
2a76ae2192 | ||
|
|
39931a3a2c | ||
|
|
58eb49617b | ||
|
|
9b621d2ec7 | ||
|
|
f2e8b652fa | ||
|
|
f342d9d561 | ||
|
|
8009bdf099 | ||
|
|
7336ce81bd | ||
|
|
f805bec8f2 | ||
|
|
46cdc84e6b | ||
|
|
e908d16ba8 | ||
|
|
96e3f4138f | ||
|
|
977dd52890 | ||
|
|
a9b7bb2175 | ||
|
|
f83c7fdac8 | ||
|
|
a695fb59b2 | ||
|
|
e197bb2900 | ||
|
|
b54b8815d9 | ||
|
|
1d1fde61be | ||
|
|
683186cbe9 | ||
|
|
5e6be40454 | ||
|
|
c00febd95b | ||
|
|
4c8e1113bf | ||
|
|
6f854da70b | ||
|
|
3f5a162a4e | ||
|
|
d14529b602 | ||
|
|
f471961f06 | ||
|
|
457c1356a4 | ||
|
|
419ac2bf50 | ||
|
|
727d088577 | ||
|
|
972d2dcc6b | ||
|
|
a5bcd49ecb | ||
|
|
ff496ad2d3 | ||
|
|
24d438ba23 | ||
|
|
591067e39e | ||
|
|
38a95dc5c4 | ||
|
|
53426d8493 | ||
|
|
a06238558e | ||
|
|
b3a3b19d5d | ||
|
|
57231c80a8 | ||
|
|
dc96fb9ad5 | ||
|
|
567847b47a | ||
|
|
b74fed7073 | ||
|
|
cdaf8ee6d5 | ||
|
|
c6c5073e47 | ||
|
|
2a58b954e9 | ||
|
|
9772623880 | ||
|
|
27b6183aad | ||
|
|
55e19618f8 | ||
|
|
5c60ba4d7f | ||
|
|
b9cc584086 | ||
|
|
bf5a8c2fad | ||
|
|
66138e5ea2 | ||
|
|
75fb89ef76 | ||
|
|
a2cb468222 | ||
|
|
166bcd7c45 | ||
|
|
95aed6af54 | ||
|
|
7a697c12af | ||
|
|
23278ce30b | ||
|
|
36a5610e25 | ||
|
|
08263c4588 | ||
|
|
1e88a52208 | ||
|
|
e770a42175 | ||
|
|
35b467c04f | ||
|
|
2001539755 | ||
|
|
e49c4bfe5a | ||
|
|
15be31fd5f | ||
|
|
4777ec46be | ||
|
|
80ab731aec | ||
|
|
080bafe18f | ||
|
|
e913f88f52 | ||
|
|
e94c5484a0 | ||
|
|
4af00c51d3 | ||
|
|
23d1256dbf | ||
|
|
ed0ef46d2e | ||
|
|
1860ded735 | ||
|
|
d432854b82 | ||
|
|
93582c8ea3 | ||
|
|
520d35b1d6 | ||
|
|
8ca3599cbf | ||
|
|
283ae74219 | ||
|
|
cebd3f92a4 | ||
|
|
4b2481d3ea | ||
|
|
f1f2a416d3 | ||
|
|
74dd97a55f | ||
|
|
3f04f7f363 | ||
|
|
800f854a12 | ||
|
|
bef9e6d54f | ||
|
|
0e4c0ae6c3 | ||
|
|
a0ea9887df | ||
|
|
0413c22e6e | ||
|
|
989ae44a6d | ||
|
|
100d314e63 | ||
|
|
dbf4e99187 | ||
|
|
7b8a85106c | ||
|
|
58c5f01e2b | ||
|
|
bc74d35651 | ||
|
|
a1f95bf25e | ||
|
|
c454e1f8a7 | ||
|
|
5a3669e1db | ||
|
|
7a2e0b3695 | ||
|
|
ce6d6269cd | ||
|
|
7d800b7080 | ||
|
|
136154e247 | ||
|
|
0a5ce1bee8 | ||
|
|
b4e2c89e2d | ||
|
|
9e2923f571 | ||
|
|
d0c1ae76f4 | ||
|
|
fe9500e6d0 | ||
|
|
79f7c73983 | ||
|
|
d04954e46f | ||
|
|
88c6ec358c | ||
|
|
d3ea01b76e | ||
|
|
0c1a9786a3 | ||
|
|
db298a349e | ||
|
|
c1bed9f578 | ||
|
|
c7cbe4ead8 | ||
|
|
4064aaf714 | ||
|
|
6ad808003a | ||
|
|
70de6101db | ||
|
|
933595ceea | ||
|
|
461c2aef60 | ||
|
|
0c49b6514c | ||
|
|
0ec425928b | ||
|
|
711701932a | ||
|
|
951e517caf | ||
|
|
8cef603d1c | ||
|
|
2a64a02211 | ||
|
|
67ef0cd5d9 | ||
|
|
9f2334362a | ||
|
|
44ff304423 | ||
|
|
f9c70d2a2b | ||
|
|
e51f7f11f1 | ||
|
|
54ef0e7465 | ||
|
|
63def00626 | ||
|
|
335c4de109 | ||
|
|
a120c2aaee | ||
|
|
43408fcb87 | ||
|
|
8432f7b0d5 | ||
|
|
bdd7884f42 | ||
|
|
485e62b5d6 | ||
|
|
0557e6cd08 | ||
|
|
b821702b8d | ||
|
|
c5b23c321e | ||
|
|
964d2d00a2 | ||
|
|
1778afa17d |
@@ -10,3 +10,4 @@ indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
spaces_around_operators = true
|
||||
max_line_length = 130
|
||||
|
||||
+2
-1
@@ -13,10 +13,11 @@ ebean-profiling*.xml
|
||||
/db
|
||||
/mydb.db
|
||||
!src/test/ddl-review/*.sql
|
||||
|
||||
profiling/
|
||||
|
||||
# Intellij project files
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
*uuid.state
|
||||
+13
-1
@@ -1,3 +1,4 @@
|
||||
dist: trusty
|
||||
sudo: required
|
||||
language: java
|
||||
jdk:
|
||||
@@ -7,17 +8,28 @@ git:
|
||||
depth: 3
|
||||
|
||||
addons:
|
||||
postgresql: "9.4"
|
||||
postgresql: "9.6"
|
||||
# apt:
|
||||
# packages:
|
||||
# - mysql-server-5.7
|
||||
# - mysql-client-core-5.7
|
||||
# - mysql-client-5.7
|
||||
|
||||
services:
|
||||
- docker
|
||||
- postgresql
|
||||
- mysql
|
||||
|
||||
before_script:
|
||||
- ./.travis/setup_database
|
||||
- mysql -u root -e 'CREATE DATABASE test_ebean;'
|
||||
- mysql -u root -e "CREATE USER 'test_ebean'@'localhost' IDENTIFIED BY 'test';"
|
||||
- mysql -u root -e "GRANT ALL ON test_ebean.* TO 'test_ebean'@'localhost';"
|
||||
|
||||
env:
|
||||
- EBEAN_DB=h2
|
||||
- EBEAN_DB=pg
|
||||
# - EBEAN_DB=mysql
|
||||
|
||||
install: true
|
||||
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
Overview of ebean.properties file
|
||||
=================================
|
||||
|
||||
|
||||
### DbMigration options
|
||||
|
||||
You can set individual values for different platforms when generating migrations
|
||||
|
||||
`dbmigration.platform.<PLATFORM>.databaseSequenceBatchSize`
|
||||
For DB's using sequences this is the number of sequence values prefetched.
|
||||
|
||||
`dbmigration.platform.<PLATFORM>.dbuuid`
|
||||
Control, how UUID generation should work - it affects DDL which column type is generated. Possible values:
|
||||
- BINARY enforces binary UUID
|
||||
- VARCHAR enforces varchar UUID
|
||||
- BINARY_OPTIMIZED enforces binary-optimized UUID (makes sense only with Type1 ID)
|
||||
- AUTO_BINARY use binary when platform does not support native UUID
|
||||
- AUTO_BINARY_OPTIMIZED use binary-optimized when platform does not support native UUID
|
||||
- AUTO_VARCHAR (default) use varchar when platform does not support UUID
|
||||
|
||||
`dbmigration.platform.<PLATFORM>.uuidStoreAsBinary`
|
||||
Same as setting dbuuid to BINARY
|
||||
|
||||
`dbmigration.platform.<PLATFORM>.geometrySRID`
|
||||
The Geometry SRID value (default 4326).
|
||||
|
||||
`dbmigration.platform.<PLATFORM>.idType`
|
||||
The ID type (IDENTITY, SEQUENCE, GENERATOR, EXTERNAL)
|
||||
|
||||
`dbmigration.platform.<PLATFORM>.mapping`
|
||||
Adjust the mapping. For example `BOOLEAN=integer(32);BIT=tinyint(3)`
|
||||
|
||||
|
||||
`ebean.migration.applyPrefix`
|
||||
Set this to "V" to be compatible with FlywayDB.
|
||||
|
||||
`ebean.migration.applySuffix`
|
||||
Defaults to '.sql'
|
||||
|
||||
`ebean.migration.dbusername`
|
||||
DB user used to run the DB migration.
|
||||
|
||||
`ebean.migration.dbpassword`
|
||||
DB password used to run the DB migration.
|
||||
|
||||
`ebean.migration.generate`
|
||||
Set to true if the DB migration should be generated on server start.
|
||||
|
||||
`ebean.migration.generatePendingDrop`
|
||||
The version of a pending drop that should be generated as the next migration.
|
||||
|
||||
`ebean.migration.includeGeneratedFileComment`
|
||||
TODO
|
||||
|
||||
`ebean.migration.metaTable`
|
||||
For running migration the DB table that holds migration execution status. Default 'db_migration'
|
||||
|
||||
`ebean.migration.migrationPath`
|
||||
Resource path for the migration xml and sql.
|
||||
|
||||
`ebean.migration.modelPath`
|
||||
Subdirectory the model xml files go into. Default 'model'
|
||||
|
||||
`ebean.migration.modelSuffix`
|
||||
Suffix. Default '.model.xml'
|
||||
|
||||
`ebean.migration.name`
|
||||
Description text that can be appended to the version to become the ddl script file name
|
||||
|
||||
`ebean.migration.patchInsertOn`
|
||||
migration versions that should be added to history without running.
|
||||
|
||||
`ebean.migration.patchResetChecksumOn`
|
||||
migration versions that should have their checksum reset and not run.
|
||||
Use this if you get a 'Checksum mismatch' error.
|
||||
|
||||
`ebean.migration.placeholders`
|
||||
A comma and equals delimited placeholders that are substituted in SQL scripts when running migration (used by DB Migration runner only).
|
||||
|
||||
`ebean.migration.platform`
|
||||
The database platform to generate migration DDL for.
|
||||
|
||||
`ebean.migration.run`
|
||||
Flag set to true means to run any outstanding migrations on startup.
|
||||
|
||||
`ebean.migration.version`
|
||||
The migration version name (typically FlywayDb compatible). Example: 1.1.1_2
|
||||
|
||||
|
||||
### Ebean UUID options
|
||||
|
||||
`ebean.uuidVersion`
|
||||
Controls, how the UUIDs are generated. Possible values
|
||||
- VERSION4 (default) generate random V4 UUIDs,
|
||||
- VERSION1 generate rfc4122 compliant Type 1 UUIDs (requires a state file)
|
||||
- VERSION1RND generate fake Type 1 UUIDs
|
||||
|
||||
Note, that V1 UUIDs in conjunction with AUTO_BINARY_OPTIMIZED will give you the best index performance, but you MUST understand how this works to avoid collisions.
|
||||
|
||||
`ebean.uuidStateFile`
|
||||
The state file that is Required to generate V1 UUIDs
|
||||
|
||||
|
||||
### DocStoreConfig
|
||||
|
||||
`ebean.docstore.active`
|
||||
True when the Document store integration is active/on.
|
||||
|
||||
`ebean.docstore.allowAllCertificates`
|
||||
Set to true such that the client allows connections to invalid/self signed SSL certificates.
|
||||
|
||||
`ebean.docstore.bulkBatchSize`
|
||||
The default batch size to use for the Bulk API calls.
|
||||
|
||||
`ebean.docstore.create`
|
||||
When true the Document store should create any document indexes that don't already exist.
|
||||
|
||||
`ebean.docstore.dropCreate`
|
||||
When true the Document store should drop and re-create document indexes.
|
||||
|
||||
`ebean.docstore.generateMapping`
|
||||
Set to true means Ebean will generate mapping files on startup.
|
||||
|
||||
`ebean.docstore.mappingPath`
|
||||
Resource path for the Document store mapping files.
|
||||
|
||||
`ebean.docstore.mappingSuffix`
|
||||
Suffix used for mapping files.
|
||||
|
||||
`ebean.docstore.password`
|
||||
Password credential that be used for authentication to document store.
|
||||
|
||||
`ebean.docstore.pathToResources`
|
||||
Location of resources that mapping files are generated into.
|
||||
|
||||
`ebean.docstore.persist`
|
||||
The default mode used by indexes. `DEFAULT|IGNORE|QUEUE|UPDATE`
|
||||
|
||||
`ebean.docstore.url`
|
||||
The URL of the Document store server. For example: http://localhost:9200.
|
||||
|
||||
`ebean.docstore.username`
|
||||
Credential that be used for authentication to document store.
|
||||
|
||||
### Ebean Autotune
|
||||
|
||||
`ebean.autoTune.mode`
|
||||
The autoTune mode `DEFAULT_OFF|DEFAULT_ON|DEFAULT_ONIFEMPTY`
|
||||
|
||||
`ebean.autoTune.profiling`
|
||||
TODO
|
||||
|
||||
`ebean.autoTune.profilingBase`
|
||||
TODO
|
||||
|
||||
`ebean.autoTune.profilingFile`
|
||||
TODO
|
||||
|
||||
`ebean.autoTune.profilingRate`
|
||||
TODO
|
||||
|
||||
`ebean.autoTune.profilingUpdateFrequency`
|
||||
TODO
|
||||
|
||||
`ebean.autoTune.queryTuning`
|
||||
TODO
|
||||
|
||||
`ebean.autoTune.queryTuningAddVersion`
|
||||
TODO
|
||||
|
||||
`ebean.autoTune.queryTuningFile`
|
||||
TODO
|
||||
|
||||
`ebean.autoTune.skipGarbageCollectionOnShutdown`
|
||||
TODO
|
||||
|
||||
`ebean.autoTune.skipProfileReportingOnShutdown`
|
||||
TODO
|
||||
|
||||
|
||||
### Ebean options
|
||||
`ebean.allQuotedIdentifiers`
|
||||
Quote all identifiers
|
||||
|
||||
`ebean.asOfSysPeriod`
|
||||
Column used to support history and 'As of' queries. This column is a timestamp range or equivalent.
|
||||
|
||||
`ebean.asOfViewSuffix`
|
||||
Suffix appended to the base table to derive the view that contains the union of the base table and the history table in order to support asOf queries.
|
||||
|
||||
`ebean.autoCommitMode`
|
||||
Set to true if the DataSource uses autoCommit. Indicates that Ebean should use autoCommit friendly Transactions and TransactionManager.
|
||||
|
||||
`ebean.autoReadOnlyDataSource`
|
||||
When true create a read only DataSource using readOnlyDataSourceConfig defaulting values from dataSourceConfig
|
||||
|
||||
`ebean.autostart`
|
||||
Should the server start all
|
||||
|
||||
`ebean.backgroundExecutorSchedulePoolSize`
|
||||
TODO
|
||||
|
||||
`ebean.backgroundExecutorShutdownSecs`
|
||||
TODO
|
||||
|
||||
and some more todos:
|
||||
`ebean.batch.mode`
|
||||
`ebean.batch.size`
|
||||
`ebean.changeLogAsync`
|
||||
`ebean.changeLogIncludeInserts`
|
||||
`ebean.classes`
|
||||
`ebean.collectQueryOrigins`
|
||||
`ebean.collectQueryStatsByNode`
|
||||
`ebean.currentUserProvider`
|
||||
`ebean.dataSourceJndiName`
|
||||
`ebean.dataTimeZone`
|
||||
`ebean.databaseBooleanFalse`
|
||||
`ebean.databaseBooleanTrue`
|
||||
`ebean.databasePlatform`
|
||||
`ebean.databasePlatformName`
|
||||
`ebean.databaseSequenceBatchSize`
|
||||
ebean.datasource.h2.adminpassword
|
||||
ebean.datasource.h2.adminusername
|
||||
`ebean.dbEncrypt`
|
||||
`ebean.dbOffline`
|
||||
ebean.ddl.createOnly
|
||||
ebean.ddl.generate
|
||||
ebean.ddl.header
|
||||
ebean.ddl.initSql
|
||||
ebean.ddl.run
|
||||
ebean.ddl.seedSql
|
||||
ebean.defaultDeleteMissingChildren
|
||||
ebean.defaultOrderById
|
||||
ebean.disableClasspathSearch
|
||||
ebean.disableL2Cache
|
||||
ebean.docStoreOnly
|
||||
ebean.docstore.active
|
||||
ebean.docstore.allowAllCertificates
|
||||
ebean.docstore.bulkBatchSize
|
||||
ebean.docstore.create
|
||||
ebean.docstore.dropCreate
|
||||
ebean.docstore.generateMapping
|
||||
ebean.docstore.mappingPath
|
||||
ebean.docstore.mappingSuffix
|
||||
ebean.docstore.password
|
||||
ebean.docstore.pathToResources
|
||||
ebean.docstore.persist
|
||||
ebean.docstore.url
|
||||
ebean.docstore.username
|
||||
ebean.encryptDeployManager
|
||||
ebean.encryptKeyManager
|
||||
ebean.encryptor
|
||||
ebean.explicitTransactionBeginMode
|
||||
ebean.expressionEqualsWithNullAsNoop
|
||||
ebean.expressionNativeIlike
|
||||
ebean.geometrySRID
|
||||
ebean.historyTableSuffix
|
||||
ebean.jdbcFetchSizeFindEach
|
||||
ebean.jdbcFetchSizeFindList
|
||||
ebean.jodaLocalTimeMode
|
||||
ebean.jsonDateTime
|
||||
ebean.jsonInclude
|
||||
ebean.lazyLoadBatchSize
|
||||
ebean.localTimeWithNanos
|
||||
ebean.namingConvention.schema
|
||||
ebean.namingConvention.sequenceFormat
|
||||
ebean.namingConvention.useForeignKeyPrefix
|
||||
ebean.namingconvention
|
||||
ebean.notifyL2CacheInForeground
|
||||
ebean.packages
|
||||
ebean.persistBatch
|
||||
ebean.persistBatchOnCascade
|
||||
ebean.persistBatchSize
|
||||
ebean.persistBatching
|
||||
ebean.persistenceContextScope
|
||||
ebean.profiling
|
||||
ebean.profiling.directory
|
||||
ebean.profiling.includeProfileIds
|
||||
ebean.profiling.minimumMicros
|
||||
ebean.profiling.profilesPerFile
|
||||
ebean.profiling.verbose
|
||||
ebean.queryBatchSize
|
||||
ebean.queryPlanTTLSeconds
|
||||
ebean.search.packages
|
||||
ebean.serverCachePlugin
|
||||
ebean.skipCacheAfterWrite
|
||||
ebean.slowQueryMillis
|
||||
ebean.tenant.catalogProvider
|
||||
ebean.tenant.currentTenantProvider
|
||||
ebean.tenant.mode
|
||||
ebean.tenant.partitionColumn
|
||||
ebean.tenant.schemaProvider
|
||||
ebean.updateAllPropertiesInBatch
|
||||
ebean.updateChangesOnly
|
||||
ebean.updatesDeleteMissingChildren
|
||||
ebean.useJavaxValidationNotNull
|
||||
ebean.useJtaTransactionManager
|
||||
|
||||
@@ -11,7 +11,7 @@ Goto [https://ebean-orm.github.io/](http://ebean-orm.github.io/ "Ebean ORM's Web
|
||||
[Maven central - all related projects](http://search.maven.org/#search%7Cga%7C1%7Cebean "maven central all related projects")
|
||||
|
||||
## Current versions
|
||||
* [](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebean/ebean) - ebean
|
||||
* [](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebean/ebean-agent) - ebean-agent
|
||||
* [](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebean/ebean-maven-plugin) - ebean-maven-plugin
|
||||
* [](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean) - ebean
|
||||
* [](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-agent) - ebean-agent
|
||||
* [](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-maven-plugin) - ebean-maven-plugin
|
||||
|
||||
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
This is a short description how to run tests
|
||||
============================================
|
||||
|
||||
Run tests from maven
|
||||
--------------------
|
||||
|
||||
Just type `mvn test` - it will run the whole test suite with the settings
|
||||
from `src/test/resources/ebean.properties`.
|
||||
|
||||
Run tests from eclipse
|
||||
----------------------
|
||||
|
||||
Running tests requires, that the classes are enhanced. By default, the
|
||||
`BaseTestCase` tries to load the enhancer-agent in a very early stage. This
|
||||
also works for most test cases, but it does not work if you try to run a
|
||||
complete test-suite, as classes that are loaded before the enhancer-agent
|
||||
kicks in will not get enhanced. To solve this problem, you need either the
|
||||
[Eclipse-Enhancer-Plugin](https://github.com/ebean-orm-tools/ebean-eclipse-enhancer)
|
||||
or add the agent to your JVM arguments (Preferences->Installed JREs) or to
|
||||
your run configuration:
|
||||
`-javaagent:<USER_HOME>/.m2/repository/io/ebean/ebean-agent/<VERSION>/ebean-agent-<VERSION>.jar=debug=0`
|
||||
|
||||
|
||||
Platform Tests
|
||||
==============
|
||||
|
||||
By default, the `h2` platform is used. To test a different platform, add
|
||||
`-Ddatasource.default=xxx` to your maven command or JVM arguments.
|
||||
|
||||
'h2' platform
|
||||
-------------
|
||||
|
||||
Reqires no setup
|
||||
|
||||
run: `mvn clean test`
|
||||
|
||||
Current status: PASS
|
||||
Tests run: 1986, Failures: 0, Errors: 0, Skipped: 16
|
||||
|
||||
'pg' platform
|
||||
-------------
|
||||
|
||||
Reqirements
|
||||
- a locally installed PostgreSQL server with PostGis.
|
||||
- Create a user "unit" with password "unit" and a database "unit"
|
||||
- Give the user all permissions to that db
|
||||
|
||||
run: `mvn clean test -Ddatasource.default=pg`
|
||||
|
||||
Current status: PASS
|
||||
Tests run: 2166, Failures: 0, Errors: 0, Skipped: 25
|
||||
|
||||
|
||||
|
||||
'mysql' platform
|
||||
-------------
|
||||
|
||||
Reqirements
|
||||
- a locally installed MySQL server.
|
||||
- Create a user "unit" with password "unit" and a database "unit"
|
||||
- Give the user all permissions to that db
|
||||
|
||||
run: `mvn clean test -Ddatasource.default=mysql`
|
||||
|
||||
Current status: PASS
|
||||
Tests run: 2166, Failures: 0, Errors: 0, Skipped: 33
|
||||
|
||||
|
||||
|
||||
'sqlserver' platform
|
||||
--------------------
|
||||
|
||||
Reqires an installed sqlserver - e.g. https://hub.docker.com/r/microsoft/mssql-server-linux/
|
||||
|
||||
- Create a user "ebean" with password "ebean" and a database "ebean_unittest"
|
||||
- Adjust the connection string and/or username in ebean.properties
|
||||
|
||||
run: `mvn clean test -Ddatasource.default=mssql`
|
||||
|
||||
Current status: PASS
|
||||
Tests run: 2166, Failures: 0, Errors: 0, Skipped: 39
|
||||
|
||||
|
||||
|
||||
'oracle' platform
|
||||
-----------------
|
||||
|
||||
Reqires a locally installed Oracle server.
|
||||
- Create a user "unit" with password "unit" with all permissions
|
||||
- The test wirtes directly to the "xe" SID - so do not use a productive server!
|
||||
- As the Oracle JDBC driver is only available on an oracle repository,
|
||||
you need a special maven setup as described [here](http://docs.oracle.com/middleware/1213/core/MAVEN/config_maven_repo.htm#MAVEN9010)
|
||||
|
||||
run: `mvn clean test -Ddatasource.default=ora -Poracle`
|
||||
|
||||
Current status: FAIL
|
||||
Tests run: 1986, Failures: 17, Errors: 34, Skipped: 16
|
||||
|
||||
|
||||
|
||||
'db2' platform
|
||||
--------------
|
||||
|
||||
Reqires a locally installed DB2 Express-C server.
|
||||
- Create a user "unit" with password "unit" with all permissions
|
||||
- Install the db2jcc4 driver (see pom.xml)
|
||||
|
||||
run: `mvn clean test -Ddatasource.default=db2 -Pdb2`
|
||||
|
||||
Current status: FAIL
|
||||
Tests run: 2166, Failures: 29, Errors: 58, Skipped: 30
|
||||
|
||||
|
||||
'sqlite' platform
|
||||
-----------------
|
||||
|
||||
Reqires no setup
|
||||
|
||||
run: `mvn clean test -Ddatasource.default=sqlite`
|
||||
|
||||
Current status: FAIL
|
||||
Tests run: 1986, Failures: 24, Errors: 86, Skipped: 16
|
||||
|
||||
After some time, the db locks up, every test takes 3 seconds and fails with
|
||||
"the database file is locked"
|
||||
|
||||
|
||||
|
||||
'hsqldb' platform
|
||||
-----------------
|
||||
|
||||
Reqires no setup
|
||||
|
||||
run: `mvn clean test -Ddatasource.default=hsqldb`
|
||||
|
||||
Current status: FAIL
|
||||
Tests run: 1980, Failures: 3, Errors: 657, Skipped: 16
|
||||
|
||||
|
||||
@Rob FYI
|
||||
The problem is https://sourceforge.net/p/hsqldb/bugs/1364/
|
||||
In `InsertHandler.getPstmt` the `meta.getIdentityDbColumns()` contains
|
||||
'id' in lowercase. Changing that to uppercase gives me this result:
|
||||
`Tests run: 1986, Failures: 2, Errors: 18, Skipped: 16`
|
||||
(as quick hack I added temporary a toUpperCase to BindableIdScalar.getIdentityColumn)
|
||||
|
||||
|
||||
'sqlanywhere' platform
|
||||
----------------------
|
||||
|
||||
TODO: not yet tested
|
||||
|
||||
|
||||
@@ -9,23 +9,103 @@
|
||||
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean</artifactId>
|
||||
<version>10.2.1</version>
|
||||
<version>11.18.6</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>ebean</name>
|
||||
<url>http://ebean-orm.github.io/</url>
|
||||
|
||||
<properties>
|
||||
<jackson-core.version>2.9.5</jackson-core.version>
|
||||
<snakeyaml.version>1.21</snakeyaml.version>
|
||||
</properties>
|
||||
|
||||
<scm>
|
||||
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
|
||||
<tag>ebean-10.2.1</tag>
|
||||
<tag>ebean-11.18.6</tag>
|
||||
</scm>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<!-- Note: to use this profile, you need credentials for the oracle repository in your
|
||||
settings.xml as described here:
|
||||
http://docs.oracle.com/middleware/1213/core/MAVEN/config_maven_repo.htm#MAVEN9010 -->
|
||||
<id>oracle</id>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.oracle.jdbc</groupId>
|
||||
<artifactId>ojdbc8</artifactId>
|
||||
<version>12.2.0.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>maven.oracle.com</id>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
<url>https://maven.oracle.com</url>
|
||||
<layout>default</layout>
|
||||
</repository>
|
||||
</repositories>
|
||||
</profile>
|
||||
<profile>
|
||||
<!-- Note: to use this profile, you need to download manually the db2jcc4 driver.
|
||||
After that, install it into your local maven repository:
|
||||
|
||||
mvn install:install-file \
|
||||
-Dfile=db2jcc4.jar \
|
||||
-DgroupId=com.ibm.jdbc \
|
||||
-DartifactId=db2jcc4 \
|
||||
-Dversion=4.23.42 \
|
||||
-Dpackaging=jar
|
||||
-->
|
||||
<id>db2</id>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.ibm.jdbc</groupId>
|
||||
<artifactId>db2jcc4</artifactId>
|
||||
<version>4.23.42</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</profile>
|
||||
</profiles>
|
||||
<dependencies>
|
||||
|
||||
<!--
|
||||
Projects are expected to explicit depend on version
|
||||
of slf4j that they want to use
|
||||
-->
|
||||
<dependency>
|
||||
<groupId>org.jetbrains</groupId>
|
||||
<artifactId>annotations</artifactId>
|
||||
<version>13.0</version>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>1.7.25</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!--
|
||||
Exclude snakeyaml if you desire - it is not strictly required.
|
||||
-->
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
<version>${snakeyaml.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!--
|
||||
Class retention Nonnull and Nullable annotations
|
||||
to assist with IDE auto-completion with Ebean API
|
||||
-->
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-jsr305</artifactId>
|
||||
<version>1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -37,63 +117,45 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-annotation</artifactId>
|
||||
<version>2.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.avaje</groupId>
|
||||
<artifactId>avaje-datasource-api</artifactId>
|
||||
<version>1.1</version>
|
||||
<version>4.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.avaje</groupId>
|
||||
<artifactId>avaje-datasource</artifactId>
|
||||
<version>2.1.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.avaje</groupId>
|
||||
<artifactId>avaje-classpath-scanner-api</artifactId>
|
||||
<version>2.2</version>
|
||||
<version>3.2.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.avaje</groupId>
|
||||
<artifactId>avaje-classpath-scanner</artifactId>
|
||||
<version>2.2.2</version>
|
||||
<version>3.1.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-dbmigration</artifactId>
|
||||
<version>10.1.4</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>[1.7.1,1.7.99)</version>
|
||||
<artifactId>ebean-migration</artifactId>
|
||||
<version>11.7.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.antlr</groupId>
|
||||
<artifactId>antlr4-runtime</artifactId>
|
||||
<version>4.6</version>
|
||||
<version>4.7.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Jackson core used internally by Ebean -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<version>2.6.5</version>
|
||||
<version>${jackson-core.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- provided scope for JsonNode support -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.6.5</version>
|
||||
<version>${jackson-core.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -130,7 +192,7 @@
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>9.4.1212.jre7</version>
|
||||
<version>42.2.2</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -143,12 +205,19 @@
|
||||
<!--<scope>test</scope>-->
|
||||
<!--</dependency>-->
|
||||
|
||||
<!--<dependency>-->
|
||||
<!--<groupId>microsoft</groupId>-->
|
||||
<!--<artifactId>sqlserver-jdbc</artifactId>-->
|
||||
<!--<version>4.2</version>-->
|
||||
<!--<scope>test</scope>-->
|
||||
<!--</dependency>-->
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-docker-run</artifactId>
|
||||
<version>1.5.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.2.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.avaje</groupId>
|
||||
@@ -160,7 +229,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-agent</artifactId>
|
||||
<version>10.1.7</version>
|
||||
<version>11.11.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -169,7 +238,7 @@
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<version>1.4.193</version>
|
||||
<version>1.4.196</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -187,10 +256,18 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.microsoft.sqlserver</groupId>
|
||||
<artifactId>mssql-jdbc</artifactId>
|
||||
<version>6.4.0.jre8</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>6.0.5</version>
|
||||
<!-- Do not upgrade to 6.0.6: https://bugs.mysql.com/bug.php?id=82896 -->
|
||||
<version>5.1.43</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -225,7 +302,7 @@
|
||||
<plugin>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-maven-plugin</artifactId>
|
||||
<version>10.1.7</version>
|
||||
<version>11.11.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>test</id>
|
||||
@@ -258,6 +335,11 @@
|
||||
<name>datasource.default</name>
|
||||
<value>${datasource.default}</value>
|
||||
</property>
|
||||
<property>
|
||||
<!-- transfer dbClockDelta parameter -->
|
||||
<name>dbClockDelta</name>
|
||||
<value>${dbClockDelta}</value>
|
||||
</property>
|
||||
</systemProperties>
|
||||
</configuration>
|
||||
</plugin>
|
||||
@@ -278,11 +360,11 @@
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.9.1</version>
|
||||
<configuration>
|
||||
<doctitle>Ebean 10</doctitle>
|
||||
<doctitle>Ebean 11</doctitle>
|
||||
<overview>src/main/java/io/ebean/overview.html</overview>
|
||||
<source>1.8</source>
|
||||
<doclet>org.avaje.doclet.PygmentsDoclet</doclet>
|
||||
<excludePackageNames>io.ebeaninternal.*:com.avaje.ebean.util:io.ebean.dbmigration.ddlgeneration:io.ebean.dbmigration.migration:io.ebean.dbmigration.migrationreader:io.ebean.dbmigration.model</excludePackageNames>
|
||||
<excludePackageNames>io.ebeaninternal.*:com.avaje.ebean.util</excludePackageNames>
|
||||
<docletArtifact>
|
||||
<groupId>org.avaje</groupId>
|
||||
<artifactId>pygments-doclet</artifactId>
|
||||
|
||||
@@ -10,6 +10,7 @@ import javax.persistence.PessimisticLockException;
|
||||
* </p>
|
||||
*/
|
||||
public class AcquireLockException extends PessimisticLockException {
|
||||
private static final long serialVersionUID = -8585962352965876691L;
|
||||
|
||||
/**
|
||||
* Create with a message and cause.
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
package io.ebean;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Provides finder functionality for use with "Dependency Injection style" use of Ebean.
|
||||
* <p>
|
||||
* Note that typically users would extend BeanRepository rather than BeanFinder.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* public class CustomerFinder extends BeanFinder<Long,Customer> {
|
||||
*
|
||||
* @Inject
|
||||
* public CustomerFinder(EbeanServer server) {
|
||||
* super(Customer.class, server);
|
||||
* }
|
||||
*
|
||||
* // ... add customer specific finders
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param <I> The ID type
|
||||
* @param <T> The Bean type
|
||||
*/
|
||||
public abstract class BeanFinder<I,T> {
|
||||
|
||||
protected final EbeanServer server;
|
||||
|
||||
protected final Class<T> type;
|
||||
|
||||
/**
|
||||
* Create with the given bean type and EbeanServer instance.
|
||||
*
|
||||
* @param type The bean type
|
||||
* @param server The EbeanServer instance typically created via Spring factory or equivalent.
|
||||
*/
|
||||
protected BeanFinder(Class<T> type, EbeanServer server) {
|
||||
this.type = type;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the EbeanServer to use.
|
||||
*/
|
||||
public EbeanServer db() {
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current transaction.
|
||||
*/
|
||||
public Transaction currentTransaction() {
|
||||
return db().currentTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush the JDBC batch on the current transaction.
|
||||
*/
|
||||
public void flush() {
|
||||
db().flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return typically a different EbeanServer to the default.
|
||||
* <p>
|
||||
* This is equivalent to {@link Ebean#getServer(String)}
|
||||
*
|
||||
* @param server The name of the EbeanServer. If this is null then the default EbeanServer is
|
||||
* returned.
|
||||
*/
|
||||
public EbeanServer db(String server) {
|
||||
return Ebean.getServer(server);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an entity reference for this ID.
|
||||
* <p>
|
||||
* Equivalent to {@link EbeanServer#getReference(Class, Object)}
|
||||
*/
|
||||
@Nonnull
|
||||
public T ref(I id) {
|
||||
return db().getReference(type, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an entity by ID.
|
||||
* <p>
|
||||
* Equivalent to {@link EbeanServer#find(Class, Object)}
|
||||
*/
|
||||
@Nullable
|
||||
public T findById(I id) {
|
||||
return db().find(type, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an entity by ID returning an Optional.
|
||||
*/
|
||||
@Nullable
|
||||
public Optional<T> findByIdOrEmpty(I id) {
|
||||
return db().find(type).setId(id).findOneOrEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a bean by Id.
|
||||
* <p>
|
||||
* Equivalent to {@link EbeanServer#delete(Class, Object)}
|
||||
*/
|
||||
public void deleteById(I id) {
|
||||
db().delete(type, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all entities of the given type.
|
||||
*/
|
||||
@Nonnull
|
||||
public List<T> findAll() {
|
||||
return query().findList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an update query.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* int rows =
|
||||
* updateQuery()
|
||||
* .set("status", Customer.Status.ACTIVE)
|
||||
* .set("updtime", new Timestamp(System.currentTimeMillis()))
|
||||
* .where()
|
||||
* .gt("id", 1000)
|
||||
* .update();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* Equivalent to {@link EbeanServer#update(Class)}
|
||||
*/
|
||||
protected UpdateQuery<T> updateQuery() {
|
||||
return db().update(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a query.
|
||||
* <p>
|
||||
* Equivalent to {@link EbeanServer#find(Class)}
|
||||
*/
|
||||
protected Query<T> query() {
|
||||
return db().find(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a native sql query.
|
||||
*/
|
||||
protected Query<T> nativeSql(String nativeSql) {
|
||||
return db().findNative(type, nativeSql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a query using the ORM query language.
|
||||
*/
|
||||
protected Query<T> query(String ormQuery) {
|
||||
return db().createQuery(type, ormQuery);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
package io.ebean;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Provides finder functionality for use with "Dependency Injection style" use of Ebean.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* @Repository
|
||||
* public class CustomerRepository extends BeanRepository<Long,Customer> {
|
||||
*
|
||||
* @Inject
|
||||
* public CustomerRepository(EbeanServer server) {
|
||||
* super(Customer.class, server);
|
||||
* }
|
||||
*
|
||||
* // ... add customer specific finders and persist logic
|
||||
*
|
||||
* public List<Customer> findByName(String nameStart) {
|
||||
* return query().where()
|
||||
* .istartsWith("name", nameStart)
|
||||
* .findList();
|
||||
* }
|
||||
*
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* @param <I> The ID type
|
||||
* @param <T> The Bean type
|
||||
*/
|
||||
public abstract class BeanRepository<I, T> extends BeanFinder<I, T> {
|
||||
|
||||
/**
|
||||
* Create with the given bean type and EbeanServer instance.
|
||||
* <p>
|
||||
* Typically users would extend BeanRepository rather than BeanFinder.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* @Inject
|
||||
* public CustomerRepository(EbeanServer server) {
|
||||
* super(Customer.class, server);
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param type The bean type
|
||||
* @param server The EbeanServer instance typically created via Spring factory or equivalent
|
||||
*/
|
||||
protected BeanRepository(Class<T> type, EbeanServer server) {
|
||||
super(type, server);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the entity bean as dirty.
|
||||
* <p>
|
||||
* This is used so that when a bean that is otherwise unmodified is updated the version
|
||||
* property is updated.
|
||||
* <p>
|
||||
* An unmodified bean that is saved or updated is normally skipped and this marks the bean as
|
||||
* dirty so that it is not skipped.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* Customer customer = customerRepository.byId(id);
|
||||
*
|
||||
* // mark the bean as dirty so that a save() or update() will
|
||||
* // increment the version property
|
||||
*
|
||||
* customerRepository.markAsDirty(customer);
|
||||
* customerRepository.save(customer);
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see EbeanServer#markAsDirty(Object)
|
||||
*/
|
||||
public void markAsDirty(T bean) {
|
||||
db().markAsDirty(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the property as unset or 'not loaded'.
|
||||
* <p>
|
||||
* This would be used to specify a property that we did not wish to include in a stateless update.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // populate an entity bean from JSON or whatever
|
||||
* Customer customer = ...;
|
||||
*
|
||||
* // mark the email property as 'unset' so that it is not
|
||||
* // included in a 'stateless update'
|
||||
* customerRepository.markPropertyUnset(customer, "email");
|
||||
*
|
||||
* customerRepository.update(customer);
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param propertyName the name of the property on the bean to be marked as 'unset'
|
||||
*/
|
||||
public void markPropertyUnset(T bean, String propertyName) {
|
||||
((EntityBean) bean)._ebean_getIntercept().setPropertyLoaded(propertyName, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert or update this entity depending on its state.
|
||||
* <p>
|
||||
* Ebean will detect if this is a new bean or a previously fetched bean and perform either an
|
||||
* insert or an update based on that.
|
||||
*
|
||||
* @see EbeanServer#save(Object)
|
||||
*/
|
||||
public void save(T bean) {
|
||||
db().save(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save all the beans in the collection.
|
||||
*/
|
||||
public int saveAll(Collection<T> bean) {
|
||||
return db().saveAll(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update this entity.
|
||||
*
|
||||
* @see EbeanServer#update(Object)
|
||||
*/
|
||||
public void update(T bean) {
|
||||
db().update(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert this entity.
|
||||
*
|
||||
* @see EbeanServer#insert(Object)
|
||||
*/
|
||||
public void insert(T bean) {
|
||||
db().insert(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete this bean.
|
||||
* <p>
|
||||
* This will return true if the bean was deleted successfully or JDBC batch is being used.
|
||||
* </p>
|
||||
* <p>
|
||||
* If there is no current transaction one will be created and committed for
|
||||
* you automatically.
|
||||
* </p>
|
||||
* <p>
|
||||
* If the Bean does not have a version property (or loaded version property) and
|
||||
* the bean does not exist then this returns false indicating that nothing was
|
||||
* deleted. Note that, if JDBC batch mode is used then this always returns true.
|
||||
* </p>
|
||||
*
|
||||
* @see EbeanServer#delete(Object)
|
||||
*/
|
||||
public boolean delete(T bean) {
|
||||
return db().delete(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all the beans in the collection.
|
||||
*/
|
||||
public int deleteAll(Collection<T> beans) {
|
||||
return db().deleteAll(beans);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a bean permanently without soft delete.
|
||||
* <p>
|
||||
* This is used when the bean contains a <code>@SoftDelete</code> property and we
|
||||
* want to perform a hard/permanent delete.
|
||||
* </p>
|
||||
*
|
||||
* @see EbeanServer#deletePermanent(Object)
|
||||
*/
|
||||
public boolean deletePermanent(T bean) {
|
||||
return db().deletePermanent(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge this entity using the default merge options.
|
||||
* <p>
|
||||
* Ebean will detect if this is a new bean or a previously fetched bean and perform either an
|
||||
* insert or an update based on that.
|
||||
*
|
||||
* @see EbeanServer#merge(Object)
|
||||
*/
|
||||
public void merge(T bean) {
|
||||
db().merge(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge this entity using the specified merge options.
|
||||
* <p>
|
||||
* Ebean will detect if this is a new bean or a previously fetched bean and perform either an
|
||||
* insert or an update based on that.
|
||||
*
|
||||
* @see EbeanServer#merge(Object, MergeOptions)
|
||||
*/
|
||||
public void merge(T bean, MergeOptions options) {
|
||||
db().merge(bean, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes this entity from the database.
|
||||
*
|
||||
* @see EbeanServer#refresh(Object)
|
||||
*/
|
||||
public void refresh(T bean) {
|
||||
db().refresh(bean);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -99,16 +98,6 @@ public interface BeanState {
|
||||
*/
|
||||
void setReadOnly(boolean readOnly);
|
||||
|
||||
/**
|
||||
* Add a propertyChangeListener.
|
||||
*/
|
||||
void addPropertyChangeListener(PropertyChangeListener listener);
|
||||
|
||||
/**
|
||||
* Remove a propertyChangeListener.
|
||||
*/
|
||||
void removePropertyChangeListener(PropertyChangeListener listener);
|
||||
|
||||
/**
|
||||
* Advanced - Used to programmatically build a partially or fully loaded
|
||||
* entity bean. First create an entity bean via
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package io.ebean;
|
||||
|
||||
/**
|
||||
* Enum to control the different cache modes for queryCache and beanCache.
|
||||
* <h3>Bean cache</h3>
|
||||
* <p>
|
||||
* The bean cache is automatically used by default on <code>@Cache</code> beans for
|
||||
* the following queries:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>findOne() by id</li>
|
||||
* <li>findOne() by natural key(s)</li>
|
||||
* <li>findList() by ids</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Bean caching needs to be explicitly turned on for queries that are findList() by natural keys.
|
||||
* </p>
|
||||
* <h3>Query cache</h3>
|
||||
* <p>
|
||||
* For query cache use note that you must be careful, what you do with the returned collection.
|
||||
* By default the returned collections are read only and you will get an exception if you try
|
||||
* to change them.
|
||||
* If you add ".setReadOnly(false)" to your query, you'll get a collection that is a clone from the
|
||||
* one in the cache. That means, changing does not affect the cache.
|
||||
* </p>
|
||||
*
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
*/
|
||||
public enum CacheMode {
|
||||
|
||||
/**
|
||||
* Do not use cache.
|
||||
*/
|
||||
OFF(false, false),
|
||||
|
||||
/**
|
||||
* Use the cache and store a result when needed.
|
||||
*/
|
||||
ON(true, true),
|
||||
|
||||
/**
|
||||
* Only used for bean caching.
|
||||
* <p>
|
||||
* The bean cache is automatically used by default on <code>@Cache</code> beans for
|
||||
* the following queries:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>findOne() by id</li>
|
||||
* <li>findOne() by natural key(s)</li>
|
||||
* <li>findList() by ids</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Bean caching needs to be explicitly turned on for queries that are findList() by natural keys.
|
||||
* </p>
|
||||
*/
|
||||
AUTO(true, true),
|
||||
|
||||
/**
|
||||
* Do not read from cache, but put beans into the cache and invalidate parts of the cache as necessary.
|
||||
* <p>
|
||||
* Use this on a query if you want to get the fresh value from database and put it into the cache.
|
||||
*/
|
||||
PUT(false, true),
|
||||
|
||||
/**
|
||||
* GET only from the cache.
|
||||
* <p>
|
||||
* This mode does not put entries into the cache or invalidate parts of the cache.
|
||||
*/
|
||||
GET(true, false);
|
||||
|
||||
private boolean get;
|
||||
private boolean put;
|
||||
|
||||
CacheMode(boolean get, boolean put) {
|
||||
this.get = get;
|
||||
this.put = put;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if value is read from cache.
|
||||
*/
|
||||
public boolean isGet() {
|
||||
return get;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if a newly loaded value (from database) is put into the cache.
|
||||
*/
|
||||
public boolean isPut() {
|
||||
return put;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.ebean;
|
||||
/**
|
||||
* Enumeration to use with {@link Query#setCountDistinct(CountDistinctOrder)}.
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
*
|
||||
*/
|
||||
public enum CountDistinctOrder {
|
||||
NO_ORDERING,
|
||||
|
||||
/** order by attribute ascending */
|
||||
ATTR_ASC,
|
||||
|
||||
/** order by attribute descending */
|
||||
ATTR_DESC,
|
||||
|
||||
/** order by count ascending and attribute ascending */
|
||||
COUNT_ASC_ATTR_ASC,
|
||||
|
||||
/** order by count ascending and attribute descending */
|
||||
COUNT_ASC_ATTR_DESC,
|
||||
|
||||
/** order by count descending and attribute ascending */
|
||||
COUNT_DESC_ATTR_ASC,
|
||||
|
||||
/** order by count descending and attribute descending */
|
||||
COUNT_DESC_ATTR_DESC,
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.io.Serializable;
|
||||
/**
|
||||
* Holds a distinct value with it's count.
|
||||
* (Used with {@link Query#findSingleAttributeList()} and {@link Query#setCountDistinct(CountDistinctOrder)}.)
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
*/
|
||||
public class CountedValue<A> implements Serializable {
|
||||
private static final long serialVersionUID = -2267971668356749695L;
|
||||
|
||||
private final A value;
|
||||
private final long count;
|
||||
|
||||
public CountedValue(A value, long count) {
|
||||
this.value = value;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public long getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public A getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return count + ": " + value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import javax.persistence.PersistenceException;
|
||||
* Thrown when a foreign key constraint is enforced.
|
||||
*/
|
||||
public class DataIntegrityException extends PersistenceException {
|
||||
private static final long serialVersionUID = -6740171949170180970L;
|
||||
|
||||
/**
|
||||
* Create with a message and cause.
|
||||
|
||||
@@ -2,7 +2,7 @@ package io.ebean;
|
||||
|
||||
import io.ebeanservice.docstore.api.DocQueryRequest;
|
||||
import io.ebeanservice.docstore.api.RawDoc;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
@@ -56,7 +56,7 @@ public interface DocumentStore {
|
||||
* Return the bean by fetching it's content from the document store.
|
||||
* If the document is not found null is returned.
|
||||
* <p>
|
||||
* Typically this is called indirectly by findUnique() on the query.
|
||||
* Typically this is called indirectly by findOne() on the query.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
@@ -64,7 +64,7 @@ public interface DocumentStore {
|
||||
* server.find(Customer.class)
|
||||
* .setUseDocStore(true)
|
||||
* .setId(42)
|
||||
* .findUnique();
|
||||
* .findOne();
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
@@ -123,11 +123,8 @@ public interface DocumentStore {
|
||||
* server.find(Order.class)
|
||||
* .setUseDocStore(true)
|
||||
* .where()... // perhaps add predicates
|
||||
* .findEach(new QueryEachConsumer<Order>() {
|
||||
* @Override
|
||||
* public void accept(Order bean) {
|
||||
* // process the bean
|
||||
* }
|
||||
* .findEach((Order order) -> {
|
||||
* // process the bean ...
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
@@ -151,7 +148,7 @@ public interface DocumentStore {
|
||||
* server.find(Order.class)
|
||||
* .setUseDocStore(true)
|
||||
* .where()... // perhaps add predicates
|
||||
* .findEachWhile(new QueryEachWhileConsumer<Order>() {
|
||||
* .findEachWhile(new Predicate<Order>() {
|
||||
* @Override
|
||||
* public void accept(Order bean) {
|
||||
* // process the bean
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
package io.ebean;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Query for performing native SQL queries that return DTO Bean's.
|
||||
* <p>
|
||||
* These beans are just normal classes. They must have public constructors
|
||||
* and setters.
|
||||
* <p>
|
||||
* Constructors with arguments are used if the number of constructor arguments
|
||||
* matches the number of columns in the resultSet.
|
||||
* </p>
|
||||
* <p>
|
||||
* If the number of columns in the resultSet is greater than the largest constructor
|
||||
* then the largest constructor is used for the first columns and remaining columns
|
||||
* are mapped by setter methods.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* // CustomerDto is just a 'bean like' class
|
||||
* // with public constructor(s) and public setter methods
|
||||
*
|
||||
* String sql = "select id, name from customer where name like :name and status_code = :status";
|
||||
*
|
||||
* List<CustomerDto> beans =
|
||||
* Ebean.findDto(CustomrDto.class, sql)
|
||||
* .setParameter("name", "Acme%")
|
||||
* .setParameter("status", "ACTIVE")
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public interface DtoQuery<T> {
|
||||
|
||||
/**
|
||||
* Execute the query returning a list.
|
||||
*/
|
||||
@Nonnull
|
||||
List<T> findList();
|
||||
|
||||
/**
|
||||
* Execute the query iterating a row at a time.
|
||||
* <p>
|
||||
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
|
||||
* </p>
|
||||
*/
|
||||
void findEach(Consumer<T> consumer);
|
||||
|
||||
/**
|
||||
* Execute the query iterating a row at a time with the ability to stop consuming part way through.
|
||||
* <p>
|
||||
* Returning false after processing a row stops the iteration through the query results.
|
||||
* </p>
|
||||
* <p>
|
||||
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
|
||||
* </p>
|
||||
*/
|
||||
void findEachWhile(Predicate<T> consumer);
|
||||
|
||||
/**
|
||||
* Execute the query returning a single bean.
|
||||
*/
|
||||
@Nullable
|
||||
T findOne();
|
||||
|
||||
/**
|
||||
* Execute the query returning an optional bean.
|
||||
*/
|
||||
@Nonnull
|
||||
Optional<T> findOneOrEmpty();
|
||||
|
||||
/**
|
||||
* The same as bind for named parameters.
|
||||
*/
|
||||
DtoQuery<T> setParameter(String name, Object value);
|
||||
|
||||
/**
|
||||
* The same as bind for positioned parameters.
|
||||
*/
|
||||
DtoQuery<T> setParameter(int position, Object value);
|
||||
|
||||
/**
|
||||
* Set the index of the first row of the results to return.
|
||||
*/
|
||||
DtoQuery<T> setFirstRow(int firstRow);
|
||||
|
||||
/**
|
||||
* Set the maximum number of query results to return.
|
||||
*/
|
||||
DtoQuery<T> setMaxRows(int maxRows);
|
||||
|
||||
/**
|
||||
* When resultSet columns are not able to be mapped to a bean property then instead of
|
||||
* throwing effectively skip reading that column.
|
||||
*/
|
||||
DtoQuery<T> setRelaxedMode();
|
||||
|
||||
/**
|
||||
* Set a label on the query to make it easier to identify queries related to query execution statistics.
|
||||
*
|
||||
* @param label A label that is unique to the DTO bean type.
|
||||
*/
|
||||
DtoQuery<T> setLabel(String label);
|
||||
|
||||
/**
|
||||
* Set a timeout on this query.
|
||||
* <p>
|
||||
* This will typically result in a call to setQueryTimeout() on a
|
||||
* preparedStatement. If the timeout occurs an exception will be thrown - this
|
||||
* will be a SQLException wrapped up in a PersistenceException.
|
||||
* </p>
|
||||
*
|
||||
* @param secs the query timeout limit in seconds. Zero means there is no limit.
|
||||
*/
|
||||
DtoQuery<T> setTimeout(int secs);
|
||||
|
||||
/**
|
||||
* A hint which for JDBC translates to the Statement.fetchSize().
|
||||
* <p>
|
||||
* Gives the JDBC driver a hint as to the number of rows that should be
|
||||
* fetched from the database when more rows are needed for ResultSet.
|
||||
* </p>
|
||||
*/
|
||||
DtoQuery<T> setBufferFetchSizeHint(int bufferFetchSizeHint);
|
||||
|
||||
}
|
||||
@@ -4,6 +4,7 @@ package io.ebean;
|
||||
* Thrown when a duplicate is attempted on a unique constraint.
|
||||
*/
|
||||
public class DuplicateKeyException extends DataIntegrityException {
|
||||
private static final long serialVersionUID = -4771932723285724817L;
|
||||
|
||||
/**
|
||||
* Create with a message and cause.
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
package io.ebean;
|
||||
|
||||
import io.ebean.annotation.TxIsolation;
|
||||
import io.ebean.cache.ServerCacheManager;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.plugin.Property;
|
||||
import io.ebean.text.csv.CsvReader;
|
||||
import io.ebean.text.json.JsonContext;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.OptimisticLockException;
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
@@ -112,6 +117,10 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
public final class Ebean {
|
||||
private static final Logger logger = LoggerFactory.getLogger(Ebean.class);
|
||||
|
||||
static {
|
||||
EbeanVersion.getVersion(); // initalizes the version class and logs the version.
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages creation and cache of EbeanServers.
|
||||
*/
|
||||
@@ -129,8 +138,7 @@ public final class Ebean {
|
||||
private final ConcurrentHashMap<String, EbeanServer> concMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Cache for synchronized read, creation and put. Protected by the monitor
|
||||
* object.
|
||||
* Cache for synchronized read, creation and put. Protected by the monitor object.
|
||||
*/
|
||||
private final HashMap<String, EbeanServer> syncMap = new HashMap<>();
|
||||
|
||||
@@ -144,17 +152,10 @@ public final class Ebean {
|
||||
private ServerManager() {
|
||||
|
||||
try {
|
||||
// skipDefaultServer is set by EbeanServerFactory
|
||||
// ... when it is creating the primaryServer
|
||||
if (PrimaryServer.isSkip()) {
|
||||
// primary server being created by EbeanServerFactory
|
||||
// ... so we should not try and create it here
|
||||
logger.debug("PrimaryServer.isSkip()");
|
||||
|
||||
} else {
|
||||
if (!PrimaryServer.isSkip()) {
|
||||
// look to see if there is a default server defined
|
||||
String defaultName = PrimaryServer.getDefaultServerName();
|
||||
logger.debug("defaultName:" + defaultName);
|
||||
logger.debug("defaultName:{}", defaultName);
|
||||
if (defaultName != null && !defaultName.trim().isEmpty()) {
|
||||
defaultServer = getWithCreate(defaultName.trim());
|
||||
}
|
||||
@@ -289,7 +290,7 @@ public final class Ebean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Backdoor for registering a mock implementation of EbeanServer as the default server.
|
||||
* Backdoor for registering a mock implementation of EbeanServer as the default server.
|
||||
*/
|
||||
protected static EbeanServer mock(String name, EbeanServer server, boolean defaultServer) {
|
||||
EbeanServer originalPrimaryServer = serverMgr.defaultServer;
|
||||
@@ -371,9 +372,10 @@ public final class Ebean {
|
||||
/**
|
||||
* Start a transaction typically specifying REQUIRES_NEW or REQUIRED semantics.
|
||||
* <p>
|
||||
* Note that this provides an try finally alternative to using {@link #execute(TxScope, TxCallable)} or
|
||||
* {@link #execute(TxScope, TxRunnable)}.
|
||||
* Note that this provides an try finally alternative to using {@link #executeCall(TxScope, Callable)} or
|
||||
* {@link #execute(TxScope, Runnable)}.
|
||||
* </p>
|
||||
* <p>
|
||||
* <h3>REQUIRES_NEW example:</h3>
|
||||
* <pre>{@code
|
||||
* // Start a new transaction. If there is a current transaction
|
||||
@@ -636,6 +638,25 @@ public final class Ebean {
|
||||
serverMgr.getDefaultServer().updateAll(beans);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the bean using the default merge options.
|
||||
*
|
||||
* @param bean The bean to merge
|
||||
*/
|
||||
public static void merge(Object bean) {
|
||||
serverMgr.getDefaultServer().merge(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the bean using the given merge options.
|
||||
*
|
||||
* @param bean The bean to merge
|
||||
* @param options The options to control the merge
|
||||
*/
|
||||
public static void merge(Object bean, MergeOptions options) {
|
||||
serverMgr.getDefaultServer().merge(bean, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save all the beans from a Collection.
|
||||
*/
|
||||
@@ -643,6 +664,65 @@ public final class Ebean {
|
||||
return serverMgr.getDefaultServer().saveAll(beans);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks the uniqueness of a bean. I.e. if the save will work. It will return the
|
||||
* properties that violates an unique / primary key. This may be done in an UI save action to
|
||||
* validate if the user has entered correct values.
|
||||
* <p>
|
||||
* Note: This method queries the DB for uniqueness of all indices, so do not use it in a batch update.
|
||||
* <p>
|
||||
* Note: This checks only the root bean!
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // there is a unique constraint on title
|
||||
*
|
||||
* Document doc = new Document();
|
||||
* doc.setTitle("One flew over the cuckoo's nest");
|
||||
* doc.setBody("clashes with doc1");
|
||||
*
|
||||
* Set<Property> properties = server().checkUniqueness(doc);
|
||||
*
|
||||
* if (properties.isEmpty()) {
|
||||
* // it is unique ... carry on
|
||||
*
|
||||
* } else {
|
||||
* // build a user friendly message
|
||||
* // to return message back to user
|
||||
*
|
||||
* String uniqueProperties = properties.toString();
|
||||
*
|
||||
* StringBuilder msg = new StringBuilder();
|
||||
*
|
||||
* properties.forEach((it)-> {
|
||||
* Object propertyValue = it.getVal(doc);
|
||||
* String propertyName = it.getName();
|
||||
* msg.append(" property["+propertyName+"] value["+propertyValue+"]");
|
||||
* });
|
||||
*
|
||||
* // uniqueProperties > [title]
|
||||
* // custom msg > property[title] value[One flew over the cuckoo's nest]
|
||||
*
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param bean The entity bean to check uniqueness on
|
||||
* @return a set of Properties if constraint validation was detected or empty list.
|
||||
*/
|
||||
@Nonnull
|
||||
public static Set<Property> checkUniqueness(Object bean) {
|
||||
return serverMgr.getDefaultServer().checkUniqueness(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #checkUniqueness(Object)}. but with given transaction.
|
||||
*/
|
||||
@Nonnull
|
||||
public static Set<Property> checkUniqueness(Object bean, Transaction transaction) {
|
||||
return serverMgr.getDefaultServer().checkUniqueness(bean, transaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the bean.
|
||||
* <p>
|
||||
@@ -822,7 +902,7 @@ public final class Ebean {
|
||||
* }</pre>
|
||||
* <p>
|
||||
* If you want more control over the query then you can use createQuery() and
|
||||
* Query.findUnique();
|
||||
* Query.findOne();
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
@@ -843,7 +923,8 @@ public final class Ebean {
|
||||
*
|
||||
* // traverse the object graph...
|
||||
*
|
||||
* Order order = query.findUnique();
|
||||
* Order order = query.findOne();
|
||||
*
|
||||
* Customer customer = order.getCustomer();
|
||||
* Address shippingAddress = customer.getShippingAddress();
|
||||
* List<OrderDetail> details = order.getDetails();
|
||||
@@ -959,7 +1040,7 @@ public final class Ebean {
|
||||
* predicates, order by, limits etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* You then use findList(), findSet(), findMap() and findUnique() to execute
|
||||
* You then use findList(), findSet(), findMap() and findOne() to execute
|
||||
* the query and return the collection or bean.
|
||||
* </p>
|
||||
* <p>
|
||||
@@ -991,7 +1072,7 @@ public final class Ebean {
|
||||
* Query<Order> query = Ebean.createQuery(Order.class, eql);
|
||||
* query.setParameter("orderId", 2);
|
||||
*
|
||||
* Order order = query.findUnique();
|
||||
* Order order = query.findOne();
|
||||
*
|
||||
* // This is the same as:
|
||||
*
|
||||
@@ -1000,7 +1081,7 @@ public final class Ebean {
|
||||
* .fetch("details")
|
||||
* .fetch("detail.product", "name")
|
||||
* .setId(2)
|
||||
* .findUnique();
|
||||
* .findOne();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
@@ -1054,6 +1135,21 @@ public final class Ebean {
|
||||
return serverMgr.getDefaultServer().findNative(beanType, nativeSql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Query for DTO beans.
|
||||
* <p>
|
||||
* DTO beans are just normal bean like classes with public constructor(s) and setters.
|
||||
* They do not need to be registered with Ebean before use.
|
||||
* </p>
|
||||
*
|
||||
* @param dtoType The type of the DTO bean the rows will be mapped into.
|
||||
* @param sql The SQL query to execute.
|
||||
* @param <T> The type of the DTO bean.
|
||||
*/
|
||||
public static <T> DtoQuery<T> findDto(Class<T> dtoType, String sql) {
|
||||
return serverMgr.getDefaultServer().findDto(dtoType, sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an Update query to perform a bulk update.
|
||||
* <p>
|
||||
@@ -1181,20 +1277,20 @@ public final class Ebean {
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public static void execute(TxScope scope, TxRunnable r) {
|
||||
public static void execute(TxScope scope, Runnable r) {
|
||||
serverMgr.getDefaultServer().execute(scope, r);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a TxRunnable in a Transaction with the default scope.
|
||||
* Execute a Runnable in a Transaction with the default scope.
|
||||
* <p>
|
||||
* The default scope runs with REQUIRED and by default will rollback on any
|
||||
* exception (checked or runtime).
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* Ebean.execute(new TxRunnable() {
|
||||
* public void run() {
|
||||
* Ebean.execute(() -> {
|
||||
*
|
||||
* User u1 = Ebean.find(User.class, 1);
|
||||
* User u2 = Ebean.find(User.class, 2);
|
||||
*
|
||||
@@ -1203,17 +1299,17 @@ public final class Ebean {
|
||||
*
|
||||
* Ebean.save(u1);
|
||||
* Ebean.save(u2);
|
||||
* }
|
||||
*
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public static void execute(TxRunnable r) {
|
||||
public static void execute(Runnable r) {
|
||||
serverMgr.getDefaultServer().execute(r);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a TxCallable in a Transaction with an explicit scope.
|
||||
* Execute a Callable in a Transaction with an explicit scope.
|
||||
* <p>
|
||||
* The scope can control the transaction type, isolation and rollback
|
||||
* semantics.
|
||||
@@ -1223,7 +1319,7 @@ public final class Ebean {
|
||||
* // set specific transactional scope settings
|
||||
* TxScope scope = TxScope.requiresNew().setIsolation(TxIsolation.SERIALIZABLE);
|
||||
*
|
||||
* Ebean.execute(scope, new TxCallable<String>() {
|
||||
* Ebean.executeCall(scope, new Callable<String>() {
|
||||
* public String call() {
|
||||
* User u1 = Ebean.find(User.class, 1);
|
||||
* ...
|
||||
@@ -1233,12 +1329,12 @@ public final class Ebean {
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public static <T> T execute(TxScope scope, TxCallable<T> c) {
|
||||
return serverMgr.getDefaultServer().execute(scope, c);
|
||||
public static <T> T executeCall(TxScope scope, Callable<T> c) {
|
||||
return serverMgr.getDefaultServer().executeCall(scope, c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a TxCallable in a Transaction with the default scope.
|
||||
* Execute a Callable in a Transaction with the default scope.
|
||||
* <p>
|
||||
* The default scope runs with REQUIRED and by default will rollback on any
|
||||
* exception (checked or runtime).
|
||||
@@ -1249,8 +1345,8 @@ public final class Ebean {
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* Ebean.execute(new TxCallable<String>() {
|
||||
* public String call() {
|
||||
* Ebean.executeCall(() -> {
|
||||
*
|
||||
* User u1 = Ebean.find(User.class, 1);
|
||||
* User u2 = Ebean.find(User.class, 2);
|
||||
*
|
||||
@@ -1261,13 +1357,13 @@ public final class Ebean {
|
||||
* Ebean.save(u2);
|
||||
*
|
||||
* return u1.getEmail();
|
||||
* }
|
||||
*
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public static <T> T execute(TxCallable<T> c) {
|
||||
return serverMgr.getDefaultServer().execute(c);
|
||||
public static <T> T executeCall(Callable<T> c) {
|
||||
return serverMgr.getDefaultServer().executeCall(c);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
package io.ebean;
|
||||
|
||||
import io.ebean.annotation.TxIsolation;
|
||||
import io.ebean.cache.ServerCacheManager;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.meta.MetaInfoManager;
|
||||
import io.ebean.plugin.Property;
|
||||
import io.ebean.plugin.SpiServer;
|
||||
import io.ebean.text.csv.CsvReader;
|
||||
import io.ebean.text.json.JsonContext;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.NonUniqueResultException;
|
||||
import javax.persistence.OptimisticLockException;
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@@ -237,7 +242,7 @@ public interface EbeanServer {
|
||||
* Query<Order> query = Ebean.createQuery(Order.class, ormQuery);
|
||||
* query.setParameter("orderId", 2);
|
||||
*
|
||||
* Order order = query.findUnique();
|
||||
* Order order = query.findOne();
|
||||
*
|
||||
* // This is the same as:
|
||||
*
|
||||
@@ -246,7 +251,7 @@ public interface EbeanServer {
|
||||
* .fetch("details")
|
||||
* .fetch("detail.product", "name")
|
||||
* .setId(2)
|
||||
* .findUnique();
|
||||
* .findOne();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
@@ -264,7 +269,7 @@ public interface EbeanServer {
|
||||
* predicates, order by, limits etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* You then use findList(), findSet(), findMap() and findUnique() to execute
|
||||
* You then use findList(), findSet(), findMap() and findOne() to execute
|
||||
* the query and return the collection or bean.
|
||||
* </p>
|
||||
* <p>
|
||||
@@ -284,7 +289,7 @@ public interface EbeanServer {
|
||||
* .fetch("details")
|
||||
* .fetch("detail.product", "name")
|
||||
* .setId(2)
|
||||
* .findUnique();
|
||||
* .findOne();
|
||||
*
|
||||
* // find some new orders ... with firstRow/maxRows
|
||||
* List<Order> orders =
|
||||
@@ -419,6 +424,32 @@ public interface EbeanServer {
|
||||
*/
|
||||
<T> Update<T> createUpdate(Class<T> beanType, String ormUpdate);
|
||||
|
||||
/**
|
||||
* Create a Query for DTO beans.
|
||||
* <p>
|
||||
* DTO beans are just normal bean like classes with public constructor(s) and setters.
|
||||
* They do not need to be registered with Ebean before use.
|
||||
* </p>
|
||||
*
|
||||
* @param dtoType The type of the DTO bean the rows will be mapped into.
|
||||
* @param sql The SQL query to execute.
|
||||
* @param <T> The type of the DTO bean.
|
||||
*/
|
||||
<T> DtoQuery<T> findDto(Class<T> dtoType, String sql);
|
||||
|
||||
/**
|
||||
* Create a named Query for DTO beans.
|
||||
* <p>
|
||||
* DTO beans are just normal bean like classes with public constructor(s) and setters.
|
||||
* They do not need to be registered with Ebean before use.
|
||||
* </p>
|
||||
*
|
||||
* @param dtoType The type of the DTO bean the rows will be mapped into.
|
||||
* @param namedQuery The name of the query
|
||||
* @param <T> The type of the DTO bean.
|
||||
*/
|
||||
<T> DtoQuery<T> createNamedDtoQuery(Class<T> dtoType, String namedQuery);
|
||||
|
||||
/**
|
||||
* Create a SqlQuery for executing native sql
|
||||
* query statements.
|
||||
@@ -490,30 +521,44 @@ public interface EbeanServer {
|
||||
* etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* <h3>Using try with resources</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // start a transaction (stored in a ThreadLocal)
|
||||
* ebeanServer.beginTransaction();
|
||||
*
|
||||
* try (Transaction txn = ebeanServer.beginTransaction()) {
|
||||
*
|
||||
* Order order = ebeanServer.find(Order.class,10);
|
||||
* ...
|
||||
* ebeanServer.save(order);
|
||||
*
|
||||
* txn.commit();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
* <p>
|
||||
* <h3>Using try finally block</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // start a transaction (stored in a ThreadLocal)
|
||||
* Transaction txn = ebeanServer.beginTransaction();
|
||||
* try {
|
||||
* Order order = ebeanServer.find(Order.class,10);
|
||||
*
|
||||
* ebeanServer.save(order);
|
||||
*
|
||||
* ebeanServer.commitTransaction();
|
||||
* txn.commit();
|
||||
*
|
||||
* } finally {
|
||||
* // rollback if we didn't commit
|
||||
* // i.e. an exception occurred before commitTransaction().
|
||||
* ebeanServer.endTransaction();
|
||||
* txn.end();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
* <p>
|
||||
* <h3>Transaction options:</h3>
|
||||
* <h3>Transaction options</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* Transaction txn = ebeanServer.beginTransaction();
|
||||
* try {
|
||||
* try (Transaction txn = ebeanServer.beginTransaction()) {
|
||||
* // explicitly turn on/off JDBC batch use
|
||||
* txn.setBatchMode(true);
|
||||
* txn.setBatchSize(50);
|
||||
@@ -534,10 +579,6 @@ public interface EbeanServer {
|
||||
* ...
|
||||
*
|
||||
* txn.commit();
|
||||
*
|
||||
* } finally {
|
||||
* // rollback if necessary
|
||||
* txn.end();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
@@ -559,27 +600,24 @@ public interface EbeanServer {
|
||||
* Start a transaction typically specifying REQUIRES_NEW or REQUIRED semantics.
|
||||
* <p>
|
||||
* <p>
|
||||
* Note that this provides an try finally alternative to using {@link #execute(TxScope, TxCallable)} or
|
||||
* {@link #execute(TxScope, TxRunnable)}.
|
||||
* Note that this provides an try finally alternative to using {@link #executeCall(TxScope, Callable)} or
|
||||
* {@link #execute(TxScope, Runnable)}.
|
||||
* </p>
|
||||
* <p>
|
||||
* <h3>REQUIRES_NEW example:</h3>
|
||||
* <pre>{@code
|
||||
* // Start a new transaction. If there is a current transaction
|
||||
* // suspend it until this transaction ends
|
||||
* Transaction txn = server.beginTransaction(TxScope.requiresNew());
|
||||
* try {
|
||||
* try (Transaction txn = server.beginTransaction(TxScope.requiresNew())) {
|
||||
*
|
||||
* ...
|
||||
*
|
||||
* // commit the transaction
|
||||
* txn.commit();
|
||||
*
|
||||
* } finally {
|
||||
* // end this transaction which:
|
||||
* // A) will rollback transaction if it has not been committed already
|
||||
* // At end this transaction will:
|
||||
* // A) will rollback transaction if it has not been committed
|
||||
* // B) will restore a previously suspended transaction
|
||||
* txn.end();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
@@ -588,20 +626,13 @@ public interface EbeanServer {
|
||||
* <pre>{@code
|
||||
*
|
||||
* // start a new transaction if there is not a current transaction
|
||||
* Transaction txn = server.beginTransaction(TxScope.required());
|
||||
* try {
|
||||
* try (Transaction txn = server.beginTransaction(TxScope.required())) {
|
||||
*
|
||||
* ...
|
||||
*
|
||||
* // commit the transaction if it was created or
|
||||
* // do nothing if there was already a current transaction
|
||||
* txn.commit();
|
||||
*
|
||||
* } finally {
|
||||
* // end this transaction which will rollback the transaction
|
||||
* // if it was created for this try finally scope and has not
|
||||
* // already been committed
|
||||
* txn.end();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
@@ -693,7 +724,7 @@ public interface EbeanServer {
|
||||
* <p>
|
||||
* <p>
|
||||
* If you want more control over the query then you can use createQuery() and
|
||||
* Query.findUnique();
|
||||
* Query.findOne();
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
@@ -713,7 +744,7 @@ public interface EbeanServer {
|
||||
* query.fetch("details.product", "name");
|
||||
*
|
||||
*
|
||||
* Order order = query.findUnique();
|
||||
* Order order = query.findOne();
|
||||
*
|
||||
* // traverse the object graph...
|
||||
*
|
||||
@@ -729,6 +760,7 @@ public interface EbeanServer {
|
||||
* @param beanType the type of entity bean to fetch
|
||||
* @param id the id value
|
||||
*/
|
||||
@Nullable
|
||||
<T> T find(Class<T> beanType, Object id);
|
||||
|
||||
/**
|
||||
@@ -772,396 +804,22 @@ public interface EbeanServer {
|
||||
* @param beanType the type of entity bean
|
||||
* @param id the id value
|
||||
*/
|
||||
@Nonnull
|
||||
<T> T getReference(Class<T> beanType, Object id);
|
||||
|
||||
/**
|
||||
* Return the number of 'top level' or 'root' entities this query should return.
|
||||
*
|
||||
* @see Query#findCount()
|
||||
* @see Query#findFutureCount()
|
||||
*/
|
||||
<T> int findCount(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return the Id values of the query as a List.
|
||||
*
|
||||
* @see Query#findIds()
|
||||
*/
|
||||
<A, T> List<A> findIds(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return a QueryIterator for the query.
|
||||
* Return the extended API for EbeanServer.
|
||||
* <p>
|
||||
* Generally using {@link #findEach(Query, Consumer, Transaction)} or
|
||||
* {@link #findEachWhile(Query, Predicate, Transaction)} is preferred
|
||||
* to findIterate(). The reason is that those methods automatically take care of
|
||||
* closing the queryIterator (and the underlying jdbc statement and resultSet).
|
||||
* The extended API has the options for executing queries that take an explicit
|
||||
* transaction as an argument.
|
||||
* </p>
|
||||
* <p>
|
||||
* This is similar to findEach in that not all the result beans need to be held
|
||||
* in memory at the same time and as such is good for processing large queries.
|
||||
* </p>
|
||||
*
|
||||
* @see Query#findIterate()
|
||||
* @see Query#findEach(Consumer)
|
||||
* @see Query#findEachWhile(Predicate)
|
||||
*/
|
||||
<T> QueryIterator<T> findIterate(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query visiting the each bean one at a time.
|
||||
* <p>
|
||||
* Unlike findList() this is suitable for processing a query that will return
|
||||
* a very large resultSet. The reason is that not all the result beans need to be
|
||||
* held in memory at the same time and instead processed one at a time.
|
||||
* </p>
|
||||
* <p>
|
||||
* Internally this query using a PersistenceContext scoped to each bean (and the
|
||||
* beans associated object graph).
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* ebeanServer.find(Order.class)
|
||||
* .where().eq("status", Order.Status.NEW)
|
||||
* .order().asc("id")
|
||||
* .findEach((Order order) -> {
|
||||
*
|
||||
* // do something with the order bean
|
||||
* System.out.println(" -- processing order ... " + order);
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see Query#findEach(Consumer)
|
||||
* @see Query#findEachWhile(Predicate)
|
||||
*/
|
||||
<T> void findEach(Query<T> query, Consumer<T> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query visiting the each bean one at a time.
|
||||
* <p>
|
||||
* Compared to findEach() this provides the ability to stop processing the query
|
||||
* results early by returning false for the QueryEachWhileConsumer.
|
||||
* </p>
|
||||
* <p>
|
||||
* Unlike findList() this is suitable for processing a query that will return
|
||||
* a very large resultSet. The reason is that not all the result beans need to be
|
||||
* held in memory at the same time and instead processed one at a time.
|
||||
* </p>
|
||||
* <p>
|
||||
* Internally this query using a PersistenceContext scoped to each bean (and the
|
||||
* beans associated object graph).
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* ebeanServer.find(Order.class)
|
||||
* .where().eq("status", Order.Status.NEW)
|
||||
* .order().asc("id")
|
||||
* .findEachWhile((Order order) -> {
|
||||
*
|
||||
* // do something with the order bean
|
||||
* System.out.println(" -- processing order ... " + order);
|
||||
*
|
||||
* boolean carryOnProcessing = ...
|
||||
* return carryOnProcessing;
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see Query#findEach(Consumer)
|
||||
* @see Query#findEachWhile(Predicate)
|
||||
*/
|
||||
<T> void findEachWhile(Query<T> query, Predicate<T> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return versions of a @History entity bean.
|
||||
* <p>
|
||||
* Generally this query is expected to be a find by id or unique predicates query.
|
||||
* It will execute the query against the history returning the versions of the bean.
|
||||
* Typically we only need to use the extended API when we do NOT want to use the
|
||||
* usual ThreadLocal based mechanism to obtain the current transaction but instead
|
||||
* supply the transaction explicitly.
|
||||
* </p>
|
||||
*/
|
||||
<T> List<Version<T>> findVersions(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute a query returning a list of beans.
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findList()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<Customer> customers =
|
||||
* ebeanServer.find(Customer.class)
|
||||
* .where().ilike("name", "rob%")
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @param query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the list of fetched beans.
|
||||
* @see Query#findList()
|
||||
*/
|
||||
<T> List<T> findList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute find row count query in a background thread.
|
||||
* <p>
|
||||
* This returns a Future object which can be used to cancel, check the
|
||||
* execution status (isDone etc) and get the value (with or without a
|
||||
* timeout).
|
||||
* </p>
|
||||
*
|
||||
* @param query the query to execute the row count on
|
||||
* @param transaction the transaction (can be null).
|
||||
* @return a Future object for the row count query
|
||||
* @see Query#findFutureCount()
|
||||
*/
|
||||
<T> FutureRowCount<T> findFutureCount(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute find Id's query in a background thread.
|
||||
* <p>
|
||||
* This returns a Future object which can be used to cancel, check the
|
||||
* execution status (isDone etc) and get the value (with or without a
|
||||
* timeout).
|
||||
* </p>
|
||||
*
|
||||
* @param query the query to execute the fetch Id's on
|
||||
* @param transaction the transaction (can be null).
|
||||
* @return a Future object for the list of Id's
|
||||
* @see Query#findFutureIds()
|
||||
*/
|
||||
<T> FutureIds<T> findFutureIds(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute find list query in a background thread returning a FutureList object.
|
||||
* <p>
|
||||
* This returns a Future object which can be used to cancel, check the
|
||||
* execution status (isDone etc) and get the value (with or without a timeout).
|
||||
* <p>
|
||||
* This query will execute in it's own PersistenceContext and using its own transaction.
|
||||
* What that means is that it will not share any bean instances with other queries.
|
||||
*
|
||||
* @param query the query to execute in the background
|
||||
* @param transaction the transaction (can be null).
|
||||
* @return a Future object for the list result of the query
|
||||
* @see Query#findFutureList()
|
||||
*/
|
||||
<T> FutureList<T> findFutureList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query using firstRow and maxRows.
|
||||
* <p>
|
||||
* The benefit of using this over findList() is that it provides functionality to get the
|
||||
* total row count etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* If maxRows is not set on the query prior to calling findPagedList() then a
|
||||
* PersistenceException is thrown.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* PagedList<Order> pagedList = Ebean.find(Order.class)
|
||||
* .setFirstRow(50)
|
||||
* .setMaxRows(20)
|
||||
* .findPagedList();
|
||||
*
|
||||
* // fetch the total row count in the background
|
||||
* pagedList.loadRowCount();
|
||||
*
|
||||
* List<Order> orders = pagedList.getList();
|
||||
* int totalRowCount = pagedList.getTotalRowCount();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return The PagedList
|
||||
* @see Query#findPagedList()
|
||||
*/
|
||||
<T> PagedList<T> findPagedList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning a set of entity beans.
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findSet()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* Set<Customer> customers =
|
||||
* ebeanServer.find(Customer.class)
|
||||
* .where().ilike("name", "rob%")
|
||||
* .findSet();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @param query the query to execute
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the set of fetched beans.
|
||||
* @see Query#findSet()
|
||||
*/
|
||||
<T> Set<T> findSet(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning the entity beans in a Map.
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findMap()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @param query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the map of fetched beans.
|
||||
* @see Query#findMap()
|
||||
*/
|
||||
<K, T> Map<K, T> findMap(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning a list of values for a single property.
|
||||
* <p>
|
||||
* <h3>Example 1:</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<String> names =
|
||||
* Ebean.find(Customer.class)
|
||||
* .select("name")
|
||||
* .orderBy().asc("name")
|
||||
* .findSingleAttributeList();
|
||||
*
|
||||
* }</pre>
|
||||
* <h3>Example 2:</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<String> names =
|
||||
* Ebean.find(Customer.class)
|
||||
* .setDistinct(true)
|
||||
* .select("name")
|
||||
* .where().eq("status", Customer.Status.NEW)
|
||||
* .orderBy().asc("name")
|
||||
* .setMaxRows(100)
|
||||
* .findSingleAttributeList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return the list of values for the selected property
|
||||
* @see Query#findSingleAttributeList()
|
||||
*/
|
||||
<A, T> List<A> findSingleAttributeList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning at most one entity bean or null (if no matching
|
||||
* bean is found).
|
||||
* <p>
|
||||
* This will throw a NonUniqueResultException if the query finds more than one result.
|
||||
* </p>
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findUnique()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @param query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the list of fetched beans.
|
||||
* @throws NonUniqueResultException if more than one result was found
|
||||
* @see Query#findUnique()
|
||||
*/
|
||||
@Nullable
|
||||
<T> T findUnique(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute as a delete query deleting the 'root level' beans that match the predicates
|
||||
* in the query.
|
||||
* <p>
|
||||
* Note that if the query includes joins then the generated delete statement may not be
|
||||
* optimal depending on the database platform.
|
||||
* </p>
|
||||
*
|
||||
* @param query the query used for the delete
|
||||
* @param transaction the transaction to use (can be null)
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @return the number of beans/rows that were deleted
|
||||
*/
|
||||
<T> int delete(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the update query returning the number of rows updated.
|
||||
* <p>
|
||||
* The update query must be created using {@link #update(Class)}.
|
||||
* </p>
|
||||
*
|
||||
* @param query the update query to execute
|
||||
* @param transaction the optional transaction to use for the update (can be null)
|
||||
* @param <T> the type of entity bean
|
||||
* @return The number of rows updated
|
||||
*/
|
||||
<T> int update(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the sql query returning a list of MapBean.
|
||||
* <p>
|
||||
* Generally you are able to use {@link SqlQuery#findList()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
*
|
||||
* @param query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the list of fetched MapBean.
|
||||
* @see SqlQuery#findList()
|
||||
*/
|
||||
List<SqlRow> findList(SqlQuery query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the SqlQuery iterating a row at a time.
|
||||
* <p>
|
||||
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
|
||||
* </p>
|
||||
*/
|
||||
void findEach(SqlQuery query, Consumer<SqlRow> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the SqlQuery iterating a row at a time with the ability to stop consuming part way through.
|
||||
* <p>
|
||||
* Returning false after processing a row stops the iteration through the query results.
|
||||
* </p>
|
||||
* <p>
|
||||
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
|
||||
* </p>
|
||||
*/
|
||||
void findEachWhile(SqlQuery query, Predicate<SqlRow> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the sql query returning a single MapBean or null.
|
||||
* <p>
|
||||
* This will throw a PersistenceException if the query found more than one
|
||||
* result.
|
||||
* </p>
|
||||
* <p>
|
||||
* Generally you are able to use {@link SqlQuery#findUnique()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
*
|
||||
* @param query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the fetched MapBean or null if none was found.
|
||||
* @see SqlQuery#findUnique()
|
||||
*/
|
||||
@Nullable
|
||||
SqlRow findUnique(SqlQuery query, Transaction transaction);
|
||||
ExtendedServer extended();
|
||||
|
||||
/**
|
||||
* Either Insert or Update the bean depending on its state.
|
||||
@@ -1436,6 +1094,61 @@ public interface EbeanServer {
|
||||
*/
|
||||
int saveAll(Collection<?> beans, Transaction transaction) throws OptimisticLockException;
|
||||
|
||||
/**
|
||||
* This method checks the uniqueness of a bean. I.e. if the save will work. It will return the
|
||||
* properties that violates an unique / primary key. This may be done in an UI save action to
|
||||
* validate if the user has entered correct values.
|
||||
* <p>
|
||||
* Note: This method queries the DB for uniqueness of all indices, so do not use it in a batch update.
|
||||
* <p>
|
||||
* Note: This checks only the root bean!
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // there is a unique constraint on title
|
||||
*
|
||||
* Document doc = new Document();
|
||||
* doc.setTitle("One flew over the cuckoo's nest");
|
||||
* doc.setBody("clashes with doc1");
|
||||
*
|
||||
* Set<Property> properties = server().checkUniqueness(doc);
|
||||
*
|
||||
* if (properties.isEmpty()) {
|
||||
* // it is unique ... carry on
|
||||
*
|
||||
* } else {
|
||||
* // build a user friendly message
|
||||
* // to return message back to user
|
||||
*
|
||||
* String uniqueProperties = properties.toString();
|
||||
*
|
||||
* StringBuilder msg = new StringBuilder();
|
||||
*
|
||||
* properties.forEach((it)-> {
|
||||
* Object propertyValue = it.getVal(doc);
|
||||
* String propertyName = it.getName();
|
||||
* msg.append(" property["+propertyName+"] value["+propertyValue+"]");
|
||||
* });
|
||||
*
|
||||
* // uniqueProperties > [title]
|
||||
* // custom msg > property[title] value[One flew over the cuckoo's nest]
|
||||
*
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param bean The entity bean to check uniqueness on
|
||||
* @return a set of Properties if constraint validation was detected or empty list.
|
||||
*/
|
||||
@Nonnull
|
||||
Set<Property> checkUniqueness(Object bean);
|
||||
|
||||
/**
|
||||
* Same as {@link #checkUniqueness(Object)}. but with given transaction.
|
||||
*/
|
||||
@Nonnull
|
||||
Set<Property> checkUniqueness(Object bean, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Marks the entity bean as dirty.
|
||||
* <p>
|
||||
@@ -1522,6 +1235,29 @@ public interface EbeanServer {
|
||||
*/
|
||||
void updateAll(Collection<?> beans, Transaction transaction) throws OptimisticLockException;
|
||||
|
||||
/**
|
||||
* Merge the bean using the default merge options (no paths specified, default delete).
|
||||
*
|
||||
* @param bean The bean to merge
|
||||
*/
|
||||
void merge(Object bean);
|
||||
|
||||
/**
|
||||
* Merge the bean using the given merge options.
|
||||
*
|
||||
* @param bean The bean to merge
|
||||
* @param options The options to control the merge
|
||||
*/
|
||||
void merge(Object bean, MergeOptions options);
|
||||
|
||||
/**
|
||||
* Merge the bean using the given merge options and a transaction.
|
||||
*
|
||||
* @param bean The bean to merge
|
||||
* @param options The options to control the merge
|
||||
*/
|
||||
void merge(Object bean, MergeOptions options, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Insert the bean.
|
||||
* <p>
|
||||
@@ -1559,7 +1295,7 @@ public interface EbeanServer {
|
||||
int execute(CallableSql callableSql, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute a TxRunnable in a Transaction with an explicit scope.
|
||||
* Execute a Runnable in a Transaction with an explicit scope.
|
||||
* <p>
|
||||
* The scope can control the transaction type, isolation and rollback
|
||||
* semantics.
|
||||
@@ -1570,7 +1306,7 @@ public interface EbeanServer {
|
||||
* // set specific transactional scope settings
|
||||
* TxScope scope = TxScope.requiresNew().setIsolation(TxIsolation.SERIALIZABLE);
|
||||
*
|
||||
* ebeanServer.execute(scope, new TxRunnable() {
|
||||
* ebeanServer.execute(scope, new Runnable() {
|
||||
* public void run() {
|
||||
* User u1 = Ebean.find(User.class, 1);
|
||||
* ...
|
||||
@@ -1579,10 +1315,10 @@ public interface EbeanServer {
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
void execute(TxScope scope, TxRunnable runnable);
|
||||
void execute(TxScope scope, Runnable runnable);
|
||||
|
||||
/**
|
||||
* Execute a TxRunnable in a Transaction with the default scope.
|
||||
* Execute a Runnable in a Transaction with the default scope.
|
||||
* <p>
|
||||
* The default scope runs with REQUIRED and by default will rollback on any
|
||||
* exception (checked or runtime).
|
||||
@@ -1590,8 +1326,8 @@ public interface EbeanServer {
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* ebeanServer.execute(new TxRunnable() {
|
||||
* public void run() {
|
||||
* ebeanServer.execute(() -> {
|
||||
*
|
||||
* User u1 = ebeanServer.find(User.class, 1);
|
||||
* User u2 = ebeanServer.find(User.class, 2);
|
||||
*
|
||||
@@ -1600,12 +1336,12 @@ public interface EbeanServer {
|
||||
*
|
||||
* ebeanServer.save(u1);
|
||||
* ebeanServer.save(u2);
|
||||
* }
|
||||
*
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
void execute(TxRunnable runnable);
|
||||
void execute(Runnable runnable);
|
||||
|
||||
/**
|
||||
* Execute a TxCallable in a Transaction with an explicit scope.
|
||||
@@ -1619,7 +1355,7 @@ public interface EbeanServer {
|
||||
* // set specific transactional scope settings
|
||||
* TxScope scope = TxScope.requiresNew().setIsolation(TxIsolation.SERIALIZABLE);
|
||||
*
|
||||
* ebeanServer.execute(scope, new TxCallable<String>() {
|
||||
* ebeanServer.executeCall(scope, new Callable<String>() {
|
||||
* public String call() {
|
||||
* User u1 = ebeanServer.find(User.class, 1);
|
||||
* ...
|
||||
@@ -1629,7 +1365,7 @@ public interface EbeanServer {
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
<T> T execute(TxScope scope, TxCallable<T> callable);
|
||||
<T> T executeCall(TxScope scope, Callable<T> callable);
|
||||
|
||||
/**
|
||||
* Execute a TxCallable in a Transaction with the default scope.
|
||||
@@ -1638,13 +1374,9 @@ public interface EbeanServer {
|
||||
* exception (checked or runtime).
|
||||
* </p>
|
||||
* <p>
|
||||
* This is basically the same as TxRunnable except that it returns an Object
|
||||
* (and you specify the return type via generics).
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* ebeanServer.execute(new TxCallable<String>() {
|
||||
* ebeanServer.executeCall(new Callable<String>() {
|
||||
* public String call() {
|
||||
* User u1 = ebeanServer.find(User.class, 1);
|
||||
* User u2 = ebeanServer.find(User.class, 2);
|
||||
@@ -1661,7 +1393,7 @@ public interface EbeanServer {
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
<T> T execute(TxCallable<T> callable);
|
||||
<T> T executeCall(Callable<T> callable);
|
||||
|
||||
/**
|
||||
* Return the manager of the server cache ("L2" cache).
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package io.ebean;
|
||||
|
||||
import io.ebean.common.SpiContainer;
|
||||
import io.ebean.config.ContainerConfig;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebeaninternal.server.lib.ShutdownManager;
|
||||
import io.ebean.service.SpiContainer;
|
||||
import io.ebean.service.SpiContainerFactory;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
/**
|
||||
* Creates EbeanServer instances.
|
||||
@@ -30,10 +31,12 @@ import java.util.Properties;
|
||||
public class EbeanServerFactory {
|
||||
|
||||
|
||||
private static final String DEFAULT_CONTAINER = "io.ebeaninternal.server.core.DefaultContainer";
|
||||
|
||||
private static SpiContainer container;
|
||||
|
||||
static {
|
||||
EbeanVersion.getVersion(); // initalizes the version class and logs the version.
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the container with clustering configuration.
|
||||
* <p>
|
||||
@@ -66,10 +69,8 @@ public class EbeanServerFactory {
|
||||
|
||||
EbeanServer server = createInternal(config);
|
||||
|
||||
if (config.isDefaultServer()) {
|
||||
PrimaryServer.setSkip(true);
|
||||
}
|
||||
if (config.isRegister()) {
|
||||
PrimaryServer.setSkip(true);
|
||||
Ebean.register(server, config.isDefaultServer());
|
||||
}
|
||||
|
||||
@@ -99,7 +100,7 @@ public class EbeanServerFactory {
|
||||
* </p>
|
||||
*/
|
||||
public static synchronized void shutdown() {
|
||||
ShutdownManager.shutdown();
|
||||
container.shutdown();
|
||||
}
|
||||
|
||||
|
||||
@@ -135,14 +136,10 @@ public class EbeanServerFactory {
|
||||
*/
|
||||
protected static SpiContainer createContainer(ContainerConfig containerConfig) {
|
||||
|
||||
String implClassName = System.getProperty("ebean.container", DEFAULT_CONTAINER);
|
||||
|
||||
try {
|
||||
Class<?> cls = Class.forName(implClassName);
|
||||
Constructor<?> constructor = cls.getConstructor(ContainerConfig.class);
|
||||
return (SpiContainer) constructor.newInstance(containerConfig);
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
Iterator<SpiContainerFactory> factories = ServiceLoader.load(SpiContainerFactory.class).iterator();
|
||||
if (factories.hasNext()) {
|
||||
return factories.next().create(containerConfig);
|
||||
}
|
||||
throw new IllegalStateException("Service loader didn't find a SpiContainerFactory?");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package io.ebean;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
/**
|
||||
* Class to determine the ebean version. (
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
*
|
||||
*/
|
||||
public class EbeanVersion {
|
||||
private EbeanVersion() {
|
||||
|
||||
}
|
||||
private static final Logger logger = LoggerFactory.getLogger(EbeanVersion.class);
|
||||
|
||||
private static String version = "unknown";
|
||||
static {
|
||||
try {
|
||||
Properties prop = new Properties();
|
||||
try (InputStream in = Ebean.class.getResourceAsStream("/META-INF/maven/io.ebean/ebean/pom.properties")) {
|
||||
if (in != null) {
|
||||
prop.load(in);
|
||||
in.close();
|
||||
version = prop.getProperty("version");
|
||||
}
|
||||
}
|
||||
logger.info("ebean version: {}", version);
|
||||
} catch (IOException e) {
|
||||
logger.warn("Could not determine ebean version: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ebean version (read from /META-INF/maven/io.ebean/ebean/pom.properties)
|
||||
*/
|
||||
public static String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import io.ebean.search.TextQueryString;
|
||||
import io.ebean.search.TextSimple;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -247,6 +246,11 @@ public interface ExpressionFactory {
|
||||
*/
|
||||
Expression icontains(String propertyName, String value);
|
||||
|
||||
/**
|
||||
* In expression using pairs of value objects.
|
||||
*/
|
||||
Expression inPairs(Pairs pairs);
|
||||
|
||||
/**
|
||||
* In - property has a value in the array of values.
|
||||
*/
|
||||
@@ -308,9 +312,9 @@ public interface ExpressionFactory {
|
||||
Expression idIn(Object... idValues);
|
||||
|
||||
/**
|
||||
* Id IN a list of Id values.
|
||||
* Id IN a collection of Id values.
|
||||
*/
|
||||
Expression idIn(List<?> idList);
|
||||
Expression idIn(Collection<?> idCollection);
|
||||
|
||||
/**
|
||||
* All Equal - Map containing property names and their values.
|
||||
@@ -323,6 +327,30 @@ public interface ExpressionFactory {
|
||||
*/
|
||||
Expression allEq(Map<String, Object> propertyMap);
|
||||
|
||||
/**
|
||||
* Add expression for ANY of the given bit flags to be set.
|
||||
*
|
||||
* @param propertyName The property that holds the flags value
|
||||
* @param flags The flags we are looking for
|
||||
*/
|
||||
Expression bitwiseAny(String propertyName, long flags);
|
||||
|
||||
/**
|
||||
* Add expression for ALL of the given bit flags to be set.
|
||||
*
|
||||
* @param propertyName The property that holds the flags value
|
||||
* @param flags The flags we are looking for
|
||||
*/
|
||||
Expression bitwiseAll(String propertyName, long flags);
|
||||
|
||||
/**
|
||||
* Add bitwise AND expression of the given bit flags to compare with the match/mask.
|
||||
*
|
||||
* @param propertyName The property that holds the flags value
|
||||
* @param flags The flags we are looking for
|
||||
*/
|
||||
Expression bitwiseAnd(String propertyName, long flags, long match);
|
||||
|
||||
/**
|
||||
* Add raw expression with a single parameter.
|
||||
* <p>
|
||||
|
||||
@@ -5,13 +5,15 @@ import io.ebean.search.MultiMatch;
|
||||
import io.ebean.search.TextCommonTerms;
|
||||
import io.ebean.search.TextQueryString;
|
||||
import io.ebean.search.TextSimple;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.NonUniqueResultException;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
@@ -118,6 +120,14 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
Query<T> asDraft();
|
||||
|
||||
/**
|
||||
* Convert the query to a DTO bean query.
|
||||
* <p>
|
||||
* We effectively use the underlying ORM query to build the SQL and then execute
|
||||
* and map it into DTO beans.
|
||||
*/
|
||||
<D> DtoQuery<D> asDto(Class<D> dtoClass);
|
||||
|
||||
/**
|
||||
* Execute using "for update" clause which results in the DB locking the record.
|
||||
*/
|
||||
@@ -191,6 +201,7 @@ public interface ExpressionList<T> {
|
||||
*
|
||||
* @see Query#findList()
|
||||
*/
|
||||
@Nonnull
|
||||
List<T> findList();
|
||||
|
||||
/**
|
||||
@@ -198,6 +209,7 @@ public interface ExpressionList<T> {
|
||||
*
|
||||
* @see Query#findIds()
|
||||
*/
|
||||
@Nonnull
|
||||
<A> List<A> findIds();
|
||||
|
||||
/**
|
||||
@@ -213,6 +225,7 @@ public interface ExpressionList<T> {
|
||||
*
|
||||
* @see Query#findSet()
|
||||
*/
|
||||
@Nonnull
|
||||
Set<T> findSet();
|
||||
|
||||
/**
|
||||
@@ -220,6 +233,7 @@ public interface ExpressionList<T> {
|
||||
*
|
||||
* @see Query#findMap()
|
||||
*/
|
||||
@Nonnull
|
||||
<K> Map<K, T> findMap();
|
||||
|
||||
/**
|
||||
@@ -252,8 +266,27 @@ public interface ExpressionList<T> {
|
||||
*
|
||||
* @return the list of values for the selected property
|
||||
*/
|
||||
@Nonnull
|
||||
<A> List<A> findSingleAttributeList();
|
||||
|
||||
/**
|
||||
* Execute a query returning a single value of a single property/column.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* String name =
|
||||
* Ebean.find(Customer.class)
|
||||
* .select("name")
|
||||
* .where().eq("id", 42)
|
||||
* .findSingleAttribute();
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
default <A> A findSingleAttribute() {
|
||||
List<A> list = findSingleAttributeList();
|
||||
return !list.isEmpty() ? list.get(0) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query returning a single bean or null (if no matching
|
||||
* bean is found).
|
||||
@@ -263,10 +296,16 @@ public interface ExpressionList<T> {
|
||||
* </p>
|
||||
*
|
||||
* @throws NonUniqueResultException if more than one result was found
|
||||
* @see Query#findUnique()
|
||||
* @see Query#findOne()
|
||||
*/
|
||||
@Nullable
|
||||
T findUnique();
|
||||
T findOne();
|
||||
|
||||
/**
|
||||
* Execute the query returning an optional bean.
|
||||
*/
|
||||
@Nonnull
|
||||
Optional<T> findOneOrEmpty();
|
||||
|
||||
/**
|
||||
* Execute find row count query in a background thread.
|
||||
@@ -278,6 +317,7 @@ public interface ExpressionList<T> {
|
||||
*
|
||||
* @return a Future object for the row count query
|
||||
*/
|
||||
@Nonnull
|
||||
FutureRowCount<T> findFutureCount();
|
||||
|
||||
/**
|
||||
@@ -290,6 +330,7 @@ public interface ExpressionList<T> {
|
||||
*
|
||||
* @return a Future object for the list of Id's
|
||||
*/
|
||||
@Nonnull
|
||||
FutureIds<T> findFutureIds();
|
||||
|
||||
/**
|
||||
@@ -302,6 +343,7 @@ public interface ExpressionList<T> {
|
||||
*
|
||||
* @return a Future object for the list result of the query
|
||||
*/
|
||||
@Nonnull
|
||||
FutureList<T> findFutureList();
|
||||
|
||||
/**
|
||||
@@ -333,6 +375,7 @@ public interface ExpressionList<T> {
|
||||
* @return The PagedList
|
||||
* @see Query#findPagedList()
|
||||
*/
|
||||
@Nonnull
|
||||
PagedList<T> findPagedList();
|
||||
|
||||
/**
|
||||
@@ -342,6 +385,7 @@ public interface ExpressionList<T> {
|
||||
* It will execute the query against the history returning the versions of the bean.
|
||||
* </p>
|
||||
*/
|
||||
@Nonnull
|
||||
List<Version<T>> findVersions();
|
||||
|
||||
/**
|
||||
@@ -351,11 +395,13 @@ public interface ExpressionList<T> {
|
||||
* It will execute the query against the history returning the versions of the bean.
|
||||
* </p>
|
||||
*/
|
||||
@Nonnull
|
||||
List<Version<T>> findVersionsBetween(Timestamp start, Timestamp end);
|
||||
|
||||
/**
|
||||
* Add some filter predicate expressions to the many property.
|
||||
*/
|
||||
@Nonnull
|
||||
ExpressionList<T> filterMany(String prop);
|
||||
|
||||
/**
|
||||
@@ -419,18 +465,67 @@ public interface ExpressionList<T> {
|
||||
Query<T> setMapKey(String mapKey);
|
||||
|
||||
/**
|
||||
* Set to true to use the query for executing this query.
|
||||
* Set to true when this query should use the bean cache.
|
||||
* <p>
|
||||
* This is now the same as setUseBeanCache(CacheMode.ON) and will be deprecated.
|
||||
* </p>
|
||||
*
|
||||
* @see Query#setUseCache(boolean)
|
||||
*/
|
||||
Query<T> setUseCache(boolean useCache);
|
||||
|
||||
/**
|
||||
* Set to true to use the query for executing this query.
|
||||
* Set the mode to use the bean cache when executing this query.
|
||||
*
|
||||
* @see Query#setBeanCacheMode(CacheMode)
|
||||
*/
|
||||
Query<T> setBeanCacheMode(CacheMode beanCacheMode);
|
||||
|
||||
/**
|
||||
* Set the {@link CacheMode} to use the query cache for executing this query.
|
||||
*
|
||||
* @see Query#setUseQueryCache(boolean)
|
||||
*/
|
||||
Query<T> setUseQueryCache(boolean useCache);
|
||||
Query<T> setUseQueryCache(CacheMode useCache);
|
||||
|
||||
/**
|
||||
* Extended version for setDistinct in conjunction with "findSingleAttributeList";
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<CountedValue<Order.Status>> orderStatusCount =
|
||||
*
|
||||
* Ebean.find(Order.class)
|
||||
* .select("status")
|
||||
* .where()
|
||||
* .gt("orderDate", LocalDate.now().minusMonths(3))
|
||||
*
|
||||
* // fetch as single attribute with a COUNT
|
||||
* .setCountDistinct(CountDistinctOrder.COUNT_DESC_ATTR_ASC)
|
||||
* .findSingleAttributeList();
|
||||
*
|
||||
* for (CountedValue<Order.Status> entry : orderStatusCount) {
|
||||
* System.out.println(" count:" + entry.getCount()+" orderStatus:" + entry.getValue() );
|
||||
* }
|
||||
*
|
||||
* // produces
|
||||
*
|
||||
* count:3 orderStatus:NEW
|
||||
* count:1 orderStatus:SHIPPED
|
||||
* count:1 orderStatus:COMPLETE
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
Query<T> setCountDistinct(CountDistinctOrder orderBy);
|
||||
|
||||
/**
|
||||
* Calls {@link #setUseQueryCache(CacheMode)} with <code>ON</code> or <code>OFF</code>.
|
||||
*
|
||||
* @see Query#setUseQueryCache(CacheMode)
|
||||
*/
|
||||
default Query<T> setUseQueryCache(boolean enabled) {
|
||||
return setUseQueryCache(enabled ? CacheMode.ON : CacheMode.OFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if this query should execute against the doc store.
|
||||
@@ -458,6 +553,11 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
Query<T> setDisableReadAuditing();
|
||||
|
||||
/**
|
||||
* Set a label on the query (to help identify query execution statistics).
|
||||
*/
|
||||
Query<T> setLabel(String label);
|
||||
|
||||
/**
|
||||
* Add expressions to the having clause.
|
||||
* <p>
|
||||
@@ -764,6 +864,11 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
ExpressionList<T> icontains(String propertyName, String value);
|
||||
|
||||
/**
|
||||
* In expression using pairs of value objects.
|
||||
*/
|
||||
ExpressionList<T> inPairs(Pairs pairs);
|
||||
|
||||
/**
|
||||
* In - using a subQuery.
|
||||
*/
|
||||
@@ -779,6 +884,36 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
ExpressionList<T> in(String propertyName, Collection<?> values);
|
||||
|
||||
/**
|
||||
* In - using a subQuery.
|
||||
* <p>
|
||||
* This is exactly the same as in() and provided due to "in" being a Kotlin keyword
|
||||
* (and hence to avoid the slightly ugly escaping when using in() in Kotlin)
|
||||
*/
|
||||
default ExpressionList<T> isIn(String propertyName, Query<?> subQuery) {
|
||||
return in(propertyName, subQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* In - property has a value in the array of values.
|
||||
* <p>
|
||||
* This is exactly the same as in() and provided due to "in" being a Kotlin keyword
|
||||
* (and hence to avoid the slightly ugly escaping when using in() in Kotlin)
|
||||
*/
|
||||
default ExpressionList<T> isIn(String propertyName, Object... values) {
|
||||
return in(propertyName, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* In - property has a value in the collection of values.
|
||||
* <p>
|
||||
* This is exactly the same as in() and provided due to "in" being a Kotlin keyword
|
||||
* (and hence to avoid the slightly ugly escaping when using in() in Kotlin)
|
||||
*/
|
||||
default ExpressionList<T> isIn(String propertyName, Collection<?> values) {
|
||||
return in(propertyName, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Not In - property has a value in the array of values.
|
||||
*/
|
||||
@@ -820,9 +955,9 @@ public interface ExpressionList<T> {
|
||||
ExpressionList<T> idIn(Object... idValues);
|
||||
|
||||
/**
|
||||
* Id IN a list of id values.
|
||||
* Id IN a collection of id values.
|
||||
*/
|
||||
ExpressionList<T> idIn(List<?> idValues);
|
||||
ExpressionList<T> idIn(Collection<?> idValues);
|
||||
|
||||
/**
|
||||
* Id Equal to - ID property is equal to the value.
|
||||
@@ -869,6 +1004,67 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
ExpressionList<T> arrayIsNotEmpty(String propertyName);
|
||||
|
||||
/**
|
||||
* Add expression for ANY of the given bit flags to be set.
|
||||
* <pre>{@code
|
||||
*
|
||||
* where().bitwiseAny("flags", BwFlags.HAS_BULK + BwFlags.HAS_COLOUR)
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param propertyName The property that holds the flags value
|
||||
* @param flags The flags we are looking for
|
||||
*/
|
||||
ExpressionList<T> bitwiseAny(String propertyName, long flags);
|
||||
|
||||
/**
|
||||
* Add expression for ALL of the given bit flags to be set.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* where().bitwiseAll("flags", BwFlags.HAS_BULK + BwFlags.HAS_COLOUR)
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param propertyName The property that holds the flags value
|
||||
* @param flags The flags we are looking for
|
||||
*/
|
||||
ExpressionList<T> bitwiseAll(String propertyName, long flags);
|
||||
|
||||
/**
|
||||
* Add expression for the given bit flags to be NOT set.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* where().bitwiseNot("flags", BwFlags.HAS_COLOUR)
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param propertyName The property that holds the flags value
|
||||
* @param flags The flags we are looking for
|
||||
*/
|
||||
ExpressionList<T> bitwiseNot(String propertyName, long flags);
|
||||
|
||||
/**
|
||||
* Add bitwise AND expression of the given bit flags to compare with the match/mask.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // Flags Bulk + Size = Size
|
||||
* // ... meaning Bulk is not set and Size is set
|
||||
*
|
||||
* long selectedFlags = BwFlags.HAS_BULK + BwFlags.HAS_SIZE;
|
||||
* long mask = BwFlags.HAS_SIZE; // Only Size flag set
|
||||
*
|
||||
* where().bitwiseAnd("flags", selectedFlags, mask)
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param propertyName The property that holds the flags value
|
||||
* @param flags The flags we are looking for
|
||||
*/
|
||||
ExpressionList<T> bitwiseAnd(String propertyName, long flags, long match);
|
||||
|
||||
/**
|
||||
* Add raw expression with a single parameter.
|
||||
* <p>
|
||||
|
||||
@@ -0,0 +1,452 @@
|
||||
package io.ebean;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.NonUniqueResultException;
|
||||
import java.time.Clock;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* The extended API for EbeanServer.
|
||||
* <p>
|
||||
* This provides the finder methods that take an explicit transaction rather than obtaining
|
||||
* the transaction from the usual mechanism (which is ThreadLocal based).
|
||||
* </p>
|
||||
* <p>
|
||||
* In general we only want to use this ExtendedServer API when we want to avoid / bypass
|
||||
* the use of the mechanism to get the current transaction and instead explicitly supply
|
||||
* the transaction to use.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that in all cases the transaction supplied can be null and in this case the EbeanServer
|
||||
* will use the normal mechanism to obtain the transaction to use.
|
||||
* </p>
|
||||
*/
|
||||
public interface ExtendedServer {
|
||||
|
||||
/**
|
||||
* Return the NOW time from the Clock.
|
||||
*/
|
||||
long clockNow();
|
||||
|
||||
/**
|
||||
* Set the Clock to use for <code>@WhenCreated</code> and <code>@WhenModified</code>.
|
||||
* <p>
|
||||
* Note that we only expect to change the Clock for testing purposes.
|
||||
* </p>
|
||||
*/
|
||||
void setClock(Clock clock);
|
||||
|
||||
/**
|
||||
* Return the number of 'top level' or 'root' entities this query should return.
|
||||
*
|
||||
* @see Query#findCount()
|
||||
* @see Query#findFutureCount()
|
||||
*/
|
||||
<T> int findCount(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return the Id values of the query as a List.
|
||||
*
|
||||
* @see Query#findIds()
|
||||
*/
|
||||
@Nonnull
|
||||
<A, T> List<A> findIds(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return a QueryIterator for the query.
|
||||
* <p>
|
||||
* Generally using {@link #findEach(Query, Consumer, Transaction)} or
|
||||
* {@link #findEachWhile(Query, Predicate, Transaction)} is preferred
|
||||
* to findIterate(). The reason is that those methods automatically take care of
|
||||
* closing the queryIterator (and the underlying jdbc statement and resultSet).
|
||||
* </p>
|
||||
* <p>
|
||||
* This is similar to findEach in that not all the result beans need to be held
|
||||
* in memory at the same time and as such is good for processing large queries.
|
||||
* </p>
|
||||
*
|
||||
* @see Query#findIterate()
|
||||
* @see Query#findEach(Consumer)
|
||||
* @see Query#findEachWhile(Predicate)
|
||||
*/
|
||||
@Nonnull
|
||||
<T> QueryIterator<T> findIterate(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query visiting the each bean one at a time.
|
||||
* <p>
|
||||
* Unlike findList() this is suitable for processing a query that will return
|
||||
* a very large resultSet. The reason is that not all the result beans need to be
|
||||
* held in memory at the same time and instead processed one at a time.
|
||||
* </p>
|
||||
* <p>
|
||||
* Internally this query using a PersistenceContext scoped to each bean (and the
|
||||
* beans associated object graph).
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* ebeanServer.find(Order.class)
|
||||
* .where().eq("status", Order.Status.NEW)
|
||||
* .order().asc("id")
|
||||
* .findEach((Order order) -> {
|
||||
*
|
||||
* // do something with the order bean
|
||||
* System.out.println(" -- processing order ... " + order);
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see Query#findEach(Consumer)
|
||||
* @see Query#findEachWhile(Predicate)
|
||||
*/
|
||||
<T> void findEach(Query<T> query, Consumer<T> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query visiting the each bean one at a time.
|
||||
* <p>
|
||||
* Compared to findEach() this provides the ability to stop processing the query
|
||||
* results early by returning false for the Predicate.
|
||||
* </p>
|
||||
* <p>
|
||||
* Unlike findList() this is suitable for processing a query that will return
|
||||
* a very large resultSet. The reason is that not all the result beans need to be
|
||||
* held in memory at the same time and instead processed one at a time.
|
||||
* </p>
|
||||
* <p>
|
||||
* Internally this query using a PersistenceContext scoped to each bean (and the
|
||||
* beans associated object graph).
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* ebeanServer.find(Order.class)
|
||||
* .where().eq("status", Order.Status.NEW)
|
||||
* .order().asc("id")
|
||||
* .findEachWhile((Order order) -> {
|
||||
*
|
||||
* // do something with the order bean
|
||||
* System.out.println(" -- processing order ... " + order);
|
||||
*
|
||||
* boolean carryOnProcessing = ...
|
||||
* return carryOnProcessing;
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see Query#findEach(Consumer)
|
||||
* @see Query#findEachWhile(Predicate)
|
||||
*/
|
||||
<T> void findEachWhile(Query<T> query, Predicate<T> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return versions of a @History entity bean.
|
||||
* <p>
|
||||
* Generally this query is expected to be a find by id or unique predicates query.
|
||||
* It will execute the query against the history returning the versions of the bean.
|
||||
* </p>
|
||||
*/
|
||||
@Nonnull
|
||||
<T> List<Version<T>> findVersions(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute a query returning a list of beans.
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findList()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<Customer> customers =
|
||||
* ebeanServer.find(Customer.class)
|
||||
* .where().ilike("name", "rob%")
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @param query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the list of fetched beans.
|
||||
* @see Query#findList()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> List<T> findList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute find row count query in a background thread.
|
||||
* <p>
|
||||
* This returns a Future object which can be used to cancel, check the
|
||||
* execution status (isDone etc) and get the value (with or without a
|
||||
* timeout).
|
||||
* </p>
|
||||
*
|
||||
* @param query the query to execute the row count on
|
||||
* @param transaction the transaction (can be null).
|
||||
* @return a Future object for the row count query
|
||||
* @see Query#findFutureCount()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> FutureRowCount<T> findFutureCount(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute find Id's query in a background thread.
|
||||
* <p>
|
||||
* This returns a Future object which can be used to cancel, check the
|
||||
* execution status (isDone etc) and get the value (with or without a
|
||||
* timeout).
|
||||
* </p>
|
||||
*
|
||||
* @param query the query to execute the fetch Id's on
|
||||
* @param transaction the transaction (can be null).
|
||||
* @return a Future object for the list of Id's
|
||||
* @see Query#findFutureIds()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> FutureIds<T> findFutureIds(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute find list query in a background thread returning a FutureList object.
|
||||
* <p>
|
||||
* This returns a Future object which can be used to cancel, check the
|
||||
* execution status (isDone etc) and get the value (with or without a timeout).
|
||||
* <p>
|
||||
* This query will execute in it's own PersistenceContext and using its own transaction.
|
||||
* What that means is that it will not share any bean instances with other queries.
|
||||
*
|
||||
* @param query the query to execute in the background
|
||||
* @param transaction the transaction (can be null).
|
||||
* @return a Future object for the list result of the query
|
||||
* @see Query#findFutureList()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> FutureList<T> findFutureList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query using firstRow and maxRows.
|
||||
* <p>
|
||||
* The benefit of using this over findList() is that it provides functionality to get the
|
||||
* total row count etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* If maxRows is not set on the query prior to calling findPagedList() then a
|
||||
* PersistenceException is thrown.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* PagedList<Order> pagedList = Ebean.find(Order.class)
|
||||
* .setFirstRow(50)
|
||||
* .setMaxRows(20)
|
||||
* .findPagedList();
|
||||
*
|
||||
* // fetch the total row count in the background
|
||||
* pagedList.loadRowCount();
|
||||
*
|
||||
* List<Order> orders = pagedList.getList();
|
||||
* int totalRowCount = pagedList.getTotalRowCount();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return The PagedList
|
||||
* @see Query#findPagedList()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> PagedList<T> findPagedList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning a set of entity beans.
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findSet()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* Set<Customer> customers =
|
||||
* ebeanServer.find(Customer.class)
|
||||
* .where().ilike("name", "rob%")
|
||||
* .findSet();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @param query the query to execute
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the set of fetched beans.
|
||||
* @see Query#findSet()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> Set<T> findSet(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning the entity beans in a Map.
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findMap()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @param query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the map of fetched beans.
|
||||
* @see Query#findMap()
|
||||
*/
|
||||
@Nonnull
|
||||
<K, T> Map<K, T> findMap(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning a list of values for a single property.
|
||||
* <p>
|
||||
* <h3>Example 1:</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<String> names =
|
||||
* Ebean.find(Customer.class)
|
||||
* .select("name")
|
||||
* .orderBy().asc("name")
|
||||
* .findSingleAttributeList();
|
||||
*
|
||||
* }</pre>
|
||||
* <h3>Example 2:</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<String> names =
|
||||
* Ebean.find(Customer.class)
|
||||
* .setDistinct(true)
|
||||
* .select("name")
|
||||
* .where().eq("status", Customer.Status.NEW)
|
||||
* .orderBy().asc("name")
|
||||
* .setMaxRows(100)
|
||||
* .findSingleAttributeList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return the list of values for the selected property
|
||||
* @see Query#findSingleAttributeList()
|
||||
*/
|
||||
@Nonnull
|
||||
<A, T> List<A> findSingleAttributeList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning at most one entity bean or null (if no matching
|
||||
* bean is found).
|
||||
* <p>
|
||||
* This will throw a NonUniqueResultException if the query finds more than one result.
|
||||
* </p>
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findOne()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @param query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the list of fetched beans.
|
||||
* @throws NonUniqueResultException if more than one result was found
|
||||
* @see Query#findOne()
|
||||
*/
|
||||
@Nullable
|
||||
<T> T findOne(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Similar to findOne() but returns an Optional (rather than nullable).
|
||||
*/
|
||||
@Nonnull
|
||||
<T> Optional<T> findOneOrEmpty(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute as a delete query deleting the 'root level' beans that match the predicates
|
||||
* in the query.
|
||||
* <p>
|
||||
* Note that if the query includes joins then the generated delete statement may not be
|
||||
* optimal depending on the database platform.
|
||||
* </p>
|
||||
*
|
||||
* @param query the query used for the delete
|
||||
* @param transaction the transaction to use (can be null)
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @return the number of beans/rows that were deleted
|
||||
*/
|
||||
<T> int delete(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the update query returning the number of rows updated.
|
||||
* <p>
|
||||
* The update query must be created using {@link EbeanServer#update(Class)}.
|
||||
* </p>
|
||||
*
|
||||
* @param query the update query to execute
|
||||
* @param transaction the optional transaction to use for the update (can be null)
|
||||
* @param <T> the type of entity bean
|
||||
* @return The number of rows updated
|
||||
*/
|
||||
<T> int update(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the sql query returning a list of MapBean.
|
||||
* <p>
|
||||
* Generally you are able to use {@link SqlQuery#findList()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
*
|
||||
* @param query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the list of fetched MapBean.
|
||||
* @see SqlQuery#findList()
|
||||
*/
|
||||
@Nonnull
|
||||
List<SqlRow> findList(SqlQuery query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the SqlQuery iterating a row at a time.
|
||||
* <p>
|
||||
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
|
||||
* </p>
|
||||
*/
|
||||
void findEach(SqlQuery query, Consumer<SqlRow> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the SqlQuery iterating a row at a time with the ability to stop consuming part way through.
|
||||
* <p>
|
||||
* Returning false after processing a row stops the iteration through the query results.
|
||||
* </p>
|
||||
* <p>
|
||||
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
|
||||
* </p>
|
||||
*/
|
||||
void findEachWhile(SqlQuery query, Predicate<SqlRow> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the sql query returning a single MapBean or null.
|
||||
* <p>
|
||||
* This will throw a PersistenceException if the query found more than one
|
||||
* result.
|
||||
* </p>
|
||||
* <p>
|
||||
* Generally you are able to use {@link SqlQuery#findOne()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
*
|
||||
* @param query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the fetched MapBean or null if none was found.
|
||||
* @see SqlQuery#findOne()
|
||||
*/
|
||||
@Nullable
|
||||
SqlRow findOne(SqlQuery query, Transaction transaction);
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.ebean;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -27,7 +28,7 @@ import java.util.List;
|
||||
* // Add your customer finder methods ...
|
||||
*
|
||||
* public Customer byName(String name) {
|
||||
* return query().eq("name", name).findUnique();
|
||||
* return query().eq("name", name).findOne();
|
||||
* }
|
||||
*
|
||||
* public List<Customer> findNew() {
|
||||
@@ -92,6 +93,20 @@ public class Finder<I, T> {
|
||||
this.serverName = serverName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current transaction.
|
||||
*/
|
||||
public Transaction currentTransaction() {
|
||||
return db().currentTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush the JDBC batch on the current transaction.
|
||||
*/
|
||||
public void flush() {
|
||||
db().flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying 'default' EbeanServer.
|
||||
* <p>
|
||||
@@ -118,6 +133,7 @@ public class Finder<I, T> {
|
||||
* <p>
|
||||
* Equivalent to {@link EbeanServer#getReference(Class, Object)}
|
||||
*/
|
||||
@Nonnull
|
||||
public T ref(I id) {
|
||||
return db().getReference(type, id);
|
||||
}
|
||||
@@ -144,10 +160,33 @@ public class Finder<I, T> {
|
||||
/**
|
||||
* Retrieves all entities of the given type.
|
||||
*/
|
||||
@Nonnull
|
||||
public List<T> all() {
|
||||
return query().findList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an update query.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* int rows =
|
||||
* finder.update()
|
||||
* .set("status", Customer.Status.ACTIVE)
|
||||
* .set("updtime", new Timestamp(System.currentTimeMillis()))
|
||||
* .where()
|
||||
* .gt("id", 1000)
|
||||
* .update();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* Equivalent to {@link EbeanServer#update(Class)}
|
||||
*/
|
||||
public UpdateQuery<T> update() {
|
||||
return db().update(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a query.
|
||||
* <p>
|
||||
@@ -170,4 +209,5 @@ public class Finder<I, T> {
|
||||
public Query<T> query(String ormQuery) {
|
||||
return db().createQuery(type, ormQuery);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,22 +6,22 @@ package io.ebean;
|
||||
public enum LikeType {
|
||||
|
||||
/**
|
||||
* You need to put in your own wildcards.
|
||||
* You need to put in your own wildcards - no escaping is performed.
|
||||
*/
|
||||
RAW,
|
||||
|
||||
/**
|
||||
* The % wildcard is added to the end of the search word.
|
||||
* The % wildcard is added to the end of the search word and search word is escaped.
|
||||
*/
|
||||
STARTS_WITH,
|
||||
|
||||
/**
|
||||
* The % wildcard is added to the beginning of the search word.
|
||||
* The % wildcard is added to the beginning of the search word and search word is escaped.
|
||||
*/
|
||||
ENDS_WITH,
|
||||
|
||||
/**
|
||||
* The % wildcard is added to the beginning and end of the search word.
|
||||
* The % wildcard is added to the beginning and end of the search word and search word is escaped.
|
||||
*/
|
||||
CONTAINS,
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Options used to control a merge. Use MergeOptionsBuilder to create an instance.
|
||||
* <p>
|
||||
* Instances of MergeOptions are thread safe and safe to share across threads.
|
||||
*/
|
||||
public interface MergeOptions {
|
||||
|
||||
/**
|
||||
* Returns true if Id values are supplied by the client.
|
||||
* <p>
|
||||
* This would be the case when for example a mobile creates data in it's own local database
|
||||
* and then sync's. In this case often the id values are UUID.
|
||||
*/
|
||||
boolean isClientGeneratedIds();
|
||||
|
||||
/**
|
||||
* Return true if delete permanent should be used and false for 'normal' delete that allows soft deletes.
|
||||
*/
|
||||
boolean isDeletePermanent();
|
||||
|
||||
/**
|
||||
* Return the paths included in the merge.
|
||||
*/
|
||||
Set<String> paths();
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Builds a MergeOptions which is immutable and thread safe.
|
||||
*/
|
||||
public class MergeOptionsBuilder {
|
||||
|
||||
private static final MOptions DEFAULT_OPTIONS = new MOptions();
|
||||
|
||||
private Set<String> paths = new LinkedHashSet<>();
|
||||
|
||||
private boolean clientGeneratedIds;
|
||||
|
||||
private boolean deletePermanent;
|
||||
|
||||
/**
|
||||
* Return the default options.
|
||||
*/
|
||||
public static MergeOptions defaultOptions() {
|
||||
return DEFAULT_OPTIONS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a path that will included in the merge.
|
||||
*
|
||||
* @param path The path relative to the root type.
|
||||
* @return The builder to chain another addPath() or build().
|
||||
*/
|
||||
public MergeOptionsBuilder addPath(String path) {
|
||||
paths.add(path);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if Id values are supplied by the client.
|
||||
* <p>
|
||||
* This would be the case when for example a mobile creates data in it's own local database
|
||||
* and then sync's. In this case often the id values are UUID.
|
||||
*/
|
||||
public MergeOptionsBuilder setClientGeneratedIds() {
|
||||
this.clientGeneratedIds = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set that deletions should use delete permanent (rather than default which allows soft deletes).
|
||||
*/
|
||||
public MergeOptionsBuilder setDeletePermanent() {
|
||||
this.deletePermanent = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and return the MergeOptions instance.
|
||||
*/
|
||||
public MergeOptions build() {
|
||||
return new MOptions(paths, clientGeneratedIds, deletePermanent);
|
||||
}
|
||||
|
||||
private static class MOptions implements MergeOptions {
|
||||
|
||||
private final boolean clientGeneratedIds;
|
||||
private final boolean deletePermanent;
|
||||
private final Set<String> paths;
|
||||
|
||||
private MOptions(){
|
||||
this.clientGeneratedIds = false;
|
||||
this.paths = new LinkedHashSet<>();
|
||||
this.deletePermanent = false;
|
||||
}
|
||||
|
||||
private MOptions(Set<String> paths, boolean clientGeneratedIds, boolean deletePermanent) {
|
||||
this.paths = paths;
|
||||
this.clientGeneratedIds = clientGeneratedIds;
|
||||
this.deletePermanent = deletePermanent;
|
||||
}
|
||||
|
||||
public Set<String> paths() {
|
||||
return paths;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClientGeneratedIds() {
|
||||
return clientGeneratedIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeletePermanent() {
|
||||
return deletePermanent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebean;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
@@ -109,11 +110,13 @@ public interface PagedList<T> {
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
@Nonnull
|
||||
Future<Integer> getFutureCount();
|
||||
|
||||
/**
|
||||
* Return the list of entities for this page.
|
||||
*/
|
||||
@Nonnull
|
||||
List<T> getList();
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Holds a list of value object pairs.
|
||||
* <p>
|
||||
* This feature is to enable use of L2 cache with complex natural keys with findList() queries in cases where the
|
||||
* IN clause is not a single property but instead a pair of properties.
|
||||
* </p>
|
||||
* <p>
|
||||
* These queries can have predicates that can be translated into a list of complex natural keys such that the L2
|
||||
* cache can be hit with these keys to obtain some or all of the beans from L2 cache rather than the DB.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // where a bean is annotated with a complex
|
||||
* // natural key made of several properties
|
||||
* @Cache(naturalKey = {"store","code","sku"})
|
||||
*
|
||||
*
|
||||
* Pairs pairs = new Pairs("sku", "code");
|
||||
* pairs.add("sj2", 1000);
|
||||
* pairs.add("sj2", 1001);
|
||||
* pairs.add("pf3", 1000);
|
||||
*
|
||||
* List<OCachedNatKeyBean3> list = Ebean.find(OCachedNatKeyBean3.class)
|
||||
* .where()
|
||||
* .eq("store", "def")
|
||||
* .inPairs(pairs) // IN clause with 'pairs' of values
|
||||
* .orderBy("sku desc")
|
||||
*
|
||||
* // query expressions cover the natural key properties
|
||||
* // so we can choose to hit the L2 bean cache if we want
|
||||
* .setUseCache(true)
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
* <h3>Important implementation Note</h3>
|
||||
* <p>
|
||||
* When binding many pairs of values we want to be able to utilise a DB index (as this type of query usually means the
|
||||
* pairs are a unique key/index or part of a unique key/index and highly selective). Currently we know we can do this
|
||||
* on any DB that supports expression/formula based indexes.
|
||||
* using a DB string concatenation formula
|
||||
* </p>
|
||||
* <p>
|
||||
* This means, the implementation converts the list of pairs into a list of strings via concatenation and we use a
|
||||
* DB concatenation formula to match. We see SQL like:
|
||||
* </p>
|
||||
* <pre>{@code sql
|
||||
*
|
||||
* ...
|
||||
* where t0.store = ? and (t0.sku||'-'||t0.code) in (?, ? )
|
||||
*
|
||||
* // bind values like: "sj2-1000", "pf3-1000"
|
||||
*
|
||||
* }</pre>
|
||||
* <p>
|
||||
* We often create a DB expression index to match the DB concat formula like:
|
||||
* </p>
|
||||
* <pre>{@code sql
|
||||
*
|
||||
* create index ix_name on table_name ((sku || '-' || code));
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public class Pairs {
|
||||
|
||||
private final String property0;
|
||||
private final String property1;
|
||||
|
||||
private final List<Entry> entries = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Character between the values when combined via DB varchar concatenation.
|
||||
*/
|
||||
private String concatSeparator = "-";
|
||||
|
||||
/**
|
||||
* Optional suffix added to DB varchar concatenation formula.
|
||||
*/
|
||||
private String concatSuffix;
|
||||
|
||||
/**
|
||||
* Create with 2 property names.
|
||||
*
|
||||
* @param property0 The property of the first value
|
||||
* @param property1 The property of the second value
|
||||
*/
|
||||
public Pairs(String property0, String property1) {
|
||||
this.property0 = property0;
|
||||
this.property1 = property1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a pair of value objects.
|
||||
* <p>
|
||||
* Both values are expected to be immutable with equals and hashCode implementations.
|
||||
* </p>
|
||||
*
|
||||
* @param a Value of the first property
|
||||
* @param b Value of the second property
|
||||
*/
|
||||
public Pairs add(Object a, Object b) {
|
||||
entries.add(new Entry(a, b));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the first property name.
|
||||
*/
|
||||
public String getProperty0() {
|
||||
return property0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the second property name.
|
||||
*/
|
||||
public String getProperty1() {
|
||||
return property1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the value pairs.
|
||||
*/
|
||||
public List<Entry> getEntries() {
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the separator character used with DB varchar concatenation to combine the 2 values.
|
||||
*/
|
||||
public String getConcatSeparator() {
|
||||
return concatSeparator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the separator character used with DB varchar concatenation to combine the 2 values.
|
||||
*/
|
||||
public Pairs setConcatSeparator(String concatSeparator) {
|
||||
this.concatSeparator = concatSeparator;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a suffix used with DB varchar concatenation to combine the 2 values.
|
||||
*/
|
||||
public String getConcatSuffix() {
|
||||
return concatSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a suffix used with DB varchar concatenation to combine the 2 values.
|
||||
*/
|
||||
public Pairs setConcatSuffix(String concatSuffix) {
|
||||
this.concatSuffix = concatSuffix;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "p0:" + property0 + " p1:" + property1 + " entries:" + entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* A pair of 2 value objects.
|
||||
* <p>
|
||||
* Used to support inPairs() expression.
|
||||
*/
|
||||
public static class Entry {
|
||||
|
||||
private final Object a;
|
||||
private final Object b;
|
||||
|
||||
/**
|
||||
* Create with values for property0 and property1 respectively.
|
||||
*
|
||||
* @param a Value of the first property
|
||||
* @param b Value of the second property
|
||||
*/
|
||||
public Entry(Object a, Object b) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{" + a + "," + b + "}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value for the first property.
|
||||
*/
|
||||
public Object getA() {
|
||||
return a;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value for the second property.
|
||||
*/
|
||||
public Object getB() {
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Entry that = (Entry) o;
|
||||
return a.equals(that.a) && b.equals(that.b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = a.hashCode();
|
||||
result = 92821 * result + b.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package io.ebean;
|
||||
|
||||
import io.ebean.config.PropertyMap;
|
||||
import io.ebean.config.properties.PropertiesLoader;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
@@ -44,7 +44,7 @@ class PrimaryServer {
|
||||
*/
|
||||
static synchronized Properties getProperties() {
|
||||
if (globalProperties == null) {
|
||||
globalProperties = PropertyMap.defaultProperties();
|
||||
globalProperties = PropertiesLoader.load();
|
||||
}
|
||||
if (defaultServerName == null) {
|
||||
defaultServerName = determineDefaultServerName();
|
||||
@@ -59,6 +59,8 @@ class PrimaryServer {
|
||||
private static String determineDefaultServerName() {
|
||||
|
||||
String defaultServerName = System.getenv("EBEAN_DB");
|
||||
defaultServerName = System.getProperty("db", defaultServerName);
|
||||
defaultServerName = System.getProperty("ebean_db", defaultServerName);
|
||||
if (isEmpty(defaultServerName)) {
|
||||
defaultServerName = System.getProperty("datasource.default");
|
||||
if (isEmpty(defaultServerName)) {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package io.ebean;
|
||||
|
||||
/**
|
||||
* A location for profiling transactions and queries.
|
||||
* <p>
|
||||
* Typically represents a class method in the form of class file and line of code that started
|
||||
* the transaction or invoked the query.
|
||||
* </p>
|
||||
*/
|
||||
public interface ProfileLocation {
|
||||
|
||||
/**
|
||||
* Create and return a new ProfileLocation.
|
||||
*/
|
||||
static ProfileLocation create() {
|
||||
return XServiceProvider.profileLocationFactory().create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a new ProfileLocation with a given lineNumber and label.
|
||||
*/
|
||||
static ProfileLocation create(int lineNumber, String label) {
|
||||
return XServiceProvider.profileLocationFactory().create(lineNumber, label);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a new ProfileLocation with a given location.
|
||||
*/
|
||||
static ProfileLocation createAt(String location) {
|
||||
return XServiceProvider.profileLocationFactory().createAt(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the location description.
|
||||
*/
|
||||
String obtain();
|
||||
|
||||
/**
|
||||
* Return a short version of the location description.
|
||||
*/
|
||||
String shortDescription();
|
||||
|
||||
/**
|
||||
* Add execution time.
|
||||
*/
|
||||
void add(long executionTime);
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
package io.ebean;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.NonUniqueResultException;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
@@ -214,11 +215,6 @@ public interface Query<T> {
|
||||
SKIPLOCKED
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the RawSql that was set to use for this query.
|
||||
*/
|
||||
RawSql getRawSql();
|
||||
|
||||
/**
|
||||
* Set RawSql to use for this query.
|
||||
*/
|
||||
@@ -240,6 +236,14 @@ public interface Query<T> {
|
||||
*/
|
||||
Query<T> asDraft();
|
||||
|
||||
/**
|
||||
* Convert the query to a DTO bean query.
|
||||
* <p>
|
||||
* We effectively use the underlying ORM query to build the SQL and then execute
|
||||
* and map it into DTO beans.
|
||||
*/
|
||||
<D> DtoQuery<D> asDto(Class<D> dtoClass);
|
||||
|
||||
/**
|
||||
* Cancel the query execution if supported by the underlying database and
|
||||
* driver.
|
||||
@@ -598,9 +602,8 @@ public interface Query<T> {
|
||||
* <p>
|
||||
* This query will execute against the EbeanServer that was used to create it.
|
||||
* </p>
|
||||
*
|
||||
* @see EbeanServer#findIds(Query, Transaction)
|
||||
*/
|
||||
@Nonnull
|
||||
<A> List<A> findIds();
|
||||
|
||||
/**
|
||||
@@ -641,6 +644,7 @@ public interface Query<T> {
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
@Nonnull
|
||||
QueryIterator<T> findIterate();
|
||||
|
||||
/**
|
||||
@@ -668,8 +672,7 @@ public interface Query<T> {
|
||||
* </p>
|
||||
* <p>
|
||||
* This method is functionally equivalent to findIterate() but instead of using an
|
||||
* iterator uses the QueryEachConsumer (SAM) interface which is better suited to use
|
||||
* with Java8 closures.
|
||||
* iterator uses the Consumer interface which is better suited to use with Java8 closures.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
@@ -698,8 +701,7 @@ public interface Query<T> {
|
||||
* </p>
|
||||
* <p>
|
||||
* This method is functionally equivalent to findIterate() but instead of using an
|
||||
* iterator uses the QueryEachWhileConsumer (SAM) interface which is better suited to use
|
||||
* with Java8 closures.
|
||||
* iterator uses the Predicate (SAM) interface which is better suited to use with Java8 closures.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
@@ -736,9 +738,8 @@ public interface Query<T> {
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see EbeanServer#findList(Query, Transaction)
|
||||
*/
|
||||
@Nonnull
|
||||
List<T> findList();
|
||||
|
||||
/**
|
||||
@@ -754,9 +755,8 @@ public interface Query<T> {
|
||||
* .findSet();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see EbeanServer#findSet(Query, Transaction)
|
||||
*/
|
||||
@Nonnull
|
||||
Set<T> findSet();
|
||||
|
||||
/**
|
||||
@@ -776,9 +776,8 @@ public interface Query<T> {
|
||||
* .findMap();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see EbeanServer#findMap(Query, Transaction)
|
||||
*/
|
||||
@Nonnull
|
||||
<K> Map<K, T> findMap();
|
||||
|
||||
/**
|
||||
@@ -811,8 +810,29 @@ public interface Query<T> {
|
||||
*
|
||||
* @return the list of values for the selected property
|
||||
*/
|
||||
@Nonnull
|
||||
<A> List<A> findSingleAttributeList();
|
||||
|
||||
/**
|
||||
* Execute a query returning a single value of a single property/column.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* String name =
|
||||
* Ebean.find(Customer.class)
|
||||
* .select("name")
|
||||
* .where().eq("id", 42)
|
||||
* .findSingleAttribute();
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
<A> A findSingleAttribute();
|
||||
|
||||
/**
|
||||
* Return true if this is countDistinct query.
|
||||
*/
|
||||
boolean isCountDistinct();
|
||||
|
||||
/**
|
||||
* Execute the query returning either a single bean or null (if no matching
|
||||
* bean is found).
|
||||
@@ -830,7 +850,7 @@ public interface Query<T> {
|
||||
* Product product =
|
||||
* ebeanServer.find(Product.class)
|
||||
* .where().eq("sku", "aa113")
|
||||
* .findUnique();
|
||||
* .findOne();
|
||||
* ...
|
||||
* }</pre>
|
||||
* <p>
|
||||
@@ -844,7 +864,7 @@ public interface Query<T> {
|
||||
* ebeanServer.find(Order.class)
|
||||
* .setId(1)
|
||||
* .fetch("details")
|
||||
* .findUnique();
|
||||
* .findOne();
|
||||
*
|
||||
* // the order details were eagerly loaded
|
||||
* List<OrderDetail> details = order.getDetails();
|
||||
@@ -854,7 +874,13 @@ public interface Query<T> {
|
||||
* @throws NonUniqueResultException if more than one result was found
|
||||
*/
|
||||
@Nullable
|
||||
T findUnique();
|
||||
T findOne();
|
||||
|
||||
/**
|
||||
* Execute the query returning an optional bean.
|
||||
*/
|
||||
@Nonnull
|
||||
Optional<T> findOneOrEmpty();
|
||||
|
||||
/**
|
||||
* Return versions of a @History entity bean.
|
||||
@@ -868,6 +894,7 @@ public interface Query<T> {
|
||||
* It will execute the query against the history returning the versions of the bean.
|
||||
* </p>
|
||||
*/
|
||||
@Nonnull
|
||||
List<Version<T>> findVersions();
|
||||
|
||||
/**
|
||||
@@ -877,6 +904,7 @@ public interface Query<T> {
|
||||
* It will execute the query against the history returning the versions of the bean.
|
||||
* </p>
|
||||
*/
|
||||
@Nonnull
|
||||
List<Version<T>> findVersionsBetween(Timestamp start, Timestamp end);
|
||||
|
||||
/**
|
||||
@@ -914,6 +942,7 @@ public interface Query<T> {
|
||||
*
|
||||
* @return a Future object for the row count query
|
||||
*/
|
||||
@Nonnull
|
||||
FutureRowCount<T> findFutureCount();
|
||||
|
||||
/**
|
||||
@@ -926,6 +955,7 @@ public interface Query<T> {
|
||||
*
|
||||
* @return a Future object for the list of Id's
|
||||
*/
|
||||
@Nonnull
|
||||
FutureIds<T> findFutureIds();
|
||||
|
||||
/**
|
||||
@@ -937,6 +967,7 @@ public interface Query<T> {
|
||||
*
|
||||
* @return a Future object for the list result of the query
|
||||
*/
|
||||
@Nonnull
|
||||
FutureList<T> findFutureList();
|
||||
|
||||
/**
|
||||
@@ -966,6 +997,7 @@ public interface Query<T> {
|
||||
*
|
||||
* @return The PagedList
|
||||
*/
|
||||
@Nonnull
|
||||
PagedList<T> findPagedList();
|
||||
|
||||
/**
|
||||
@@ -1012,7 +1044,7 @@ public interface Query<T> {
|
||||
Query<T> setParameter(int position, Object value);
|
||||
|
||||
/**
|
||||
* Set the Id value to query. This is used with findUnique().
|
||||
* Set the Id value to query. This is used with findOne().
|
||||
* <p>
|
||||
* You can use this to have further control over the query. For example adding
|
||||
* fetch joins.
|
||||
@@ -1023,7 +1055,7 @@ public interface Query<T> {
|
||||
* ebeanServer.find(Order.class)
|
||||
* .setId(1)
|
||||
* .fetch("details")
|
||||
* .findUnique();
|
||||
* .findOne();
|
||||
*
|
||||
* // the order details were eagerly fetched
|
||||
* List<OrderDetail> details = order.getDetails();
|
||||
@@ -1245,6 +1277,37 @@ public interface Query<T> {
|
||||
*/
|
||||
Query<T> setDistinct(boolean isDistinct);
|
||||
|
||||
/**
|
||||
* Extended version for setDistinct in conjunction with "findSingleAttributeList";
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<CountedValue<Order.Status>> orderStatusCount =
|
||||
*
|
||||
* Ebean.find(Order.class)
|
||||
* .select("status")
|
||||
* .where()
|
||||
* .gt("orderDate", LocalDate.now().minusMonths(3))
|
||||
*
|
||||
* // fetch as single attribute with a COUNT
|
||||
* .setCountDistinct(CountDistinctOrder.COUNT_DESC_ATTR_ASC)
|
||||
* .findSingleAttributeList();
|
||||
*
|
||||
* for (CountedValue<Order.Status> entry : orderStatusCount) {
|
||||
* System.out.println(" count:" + entry.getCount()+" orderStatus:" + entry.getValue() );
|
||||
* }
|
||||
*
|
||||
* // produces
|
||||
*
|
||||
* count:3 orderStatus:NEW
|
||||
* count:1 orderStatus:SHIPPED
|
||||
* count:1 orderStatus:COMPLETE
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
*/
|
||||
Query<T> setCountDistinct(CountDistinctOrder orderBy);
|
||||
|
||||
/**
|
||||
* Return the first row value.
|
||||
*/
|
||||
@@ -1278,7 +1341,7 @@ public interface Query<T> {
|
||||
*
|
||||
* // Assuming sku is unique for products...
|
||||
*
|
||||
* Map<?,Product> productMap =
|
||||
* Map<String,Product> productMap =
|
||||
* ebeanServer.find(Product.class)
|
||||
* // use sku for keys...
|
||||
* .setMapKey("sku")
|
||||
@@ -1293,22 +1356,69 @@ public interface Query<T> {
|
||||
/**
|
||||
* Set this to false to not use the bean cache.
|
||||
* <p>
|
||||
* This method is now superseded by {@link #setBeanCacheMode(CacheMode)}
|
||||
* which provides more explicit options controlled bean cache use.
|
||||
* </p>
|
||||
* <p>
|
||||
* This method is likely to be deprecated in the future with migration
|
||||
* over to setUseBeanCache().
|
||||
* </p>
|
||||
*/
|
||||
default Query<T> setUseCache(boolean useCache) {
|
||||
return setBeanCacheMode(useCache ? CacheMode.ON : CacheMode.OFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the mode to use the bean cache when executing this query.
|
||||
* <p>
|
||||
* By default "find by id" and "find by natural key" will use the bean cache
|
||||
* when bean caching is enabled. Setting this to false means that the query
|
||||
* will not use the bean cache and instead hit the database.
|
||||
* </p>
|
||||
* <p>
|
||||
* In the case of other queries (findList(), findEach() etc) then setting this to
|
||||
* false beans that the if lazy loading is invoked that lazy loading will not try
|
||||
* to use the bean cache.
|
||||
* By default findList() with natural keys will not use the bean cache. In that
|
||||
* case we need to explicitly use the bean cache.
|
||||
* </p>
|
||||
*/
|
||||
Query<T> setUseCache(boolean useCache);
|
||||
Query<T> setBeanCacheMode(CacheMode beanCacheMode);
|
||||
|
||||
/**
|
||||
* Set this to true to use the query cache.
|
||||
* Set the {@link CacheMode} to use the query for executing this query.
|
||||
*/
|
||||
Query<T> setUseQueryCache(boolean useQueryCache);
|
||||
Query<T> setUseQueryCache(CacheMode queryCacheMode);
|
||||
|
||||
/**
|
||||
* Calls {@link #setUseQueryCache(CacheMode)} with <code>ON</code> or <code>OFF</code>.
|
||||
*/
|
||||
default Query<T> setUseQueryCache(boolean enabled) {
|
||||
return setUseQueryCache(enabled ? CacheMode.ON : CacheMode.OFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an id to identify this query for profiling purposes.
|
||||
* <p>
|
||||
* The profileId is expected to be unique for a given bean type.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that the profileId is treated as a short internally and has a MAX value of 32,767.
|
||||
* </p>
|
||||
*/
|
||||
Query<T> setProfileId(int profileId);
|
||||
|
||||
/**
|
||||
* Set the profile location of this query. This is used to relate query execution metrics
|
||||
* back to a location like a specific line of code.
|
||||
*/
|
||||
Query<T> setProfileLocation(ProfileLocation profileLocation);
|
||||
|
||||
/**
|
||||
* Set a label on the query.
|
||||
* <p>
|
||||
* This label can be used to help identify query performance metrics but we can also use
|
||||
* profile location enhancement on Finders so for some that would be a better option.
|
||||
* </p>
|
||||
*/
|
||||
Query<T> setLabel(String label);
|
||||
|
||||
/**
|
||||
* Set to true if this query should execute against the doc store.
|
||||
@@ -1324,8 +1434,9 @@ public interface Query<T> {
|
||||
Query<T> setReadOnly(boolean readOnly);
|
||||
|
||||
/**
|
||||
* When set to true all the beans from this query are loaded into the bean
|
||||
* cache.
|
||||
* Will be deprecated - migrate to use setBeanCacheMode(CacheMode.RECACHE).
|
||||
* <p>
|
||||
* When set to true all the beans from this query are loaded into the bean cache.
|
||||
*/
|
||||
Query<T> setLoadBeanCache(boolean loadBeanCache);
|
||||
|
||||
@@ -1370,9 +1481,12 @@ public interface Query<T> {
|
||||
String getGeneratedSql();
|
||||
|
||||
/**
|
||||
* Deprecated - migrate to forUpdate().
|
||||
*
|
||||
* executed the select with "for update" which should lock the record
|
||||
* "on read"
|
||||
*/
|
||||
@Deprecated
|
||||
Query<T> setForUpdate(boolean forUpdate);
|
||||
|
||||
/**
|
||||
@@ -1416,6 +1530,11 @@ public interface Query<T> {
|
||||
*/
|
||||
Class<T> getBeanType();
|
||||
|
||||
/**
|
||||
* Return the type of query being executed.
|
||||
*/
|
||||
QueryType getQueryType();
|
||||
|
||||
/**
|
||||
* Set true if you want to disable lazy loading.
|
||||
* <p>
|
||||
@@ -1433,4 +1552,12 @@ public interface Query<T> {
|
||||
*/
|
||||
Set<String> validate();
|
||||
|
||||
/**
|
||||
* Controls, if paginated queries should always append an 'order by id' statement at the end to
|
||||
* guarantee a deterministic sort result. This may affect performance.
|
||||
* If this is not enabled, and an orderBy is set on the query, it's up to the programmer that
|
||||
* this query provides a deterministic result.
|
||||
*/
|
||||
Query<T> orderById(boolean orderById);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package io.ebean;
|
||||
|
||||
/**
|
||||
* The type of the query being executed.
|
||||
*/
|
||||
public enum QueryType {
|
||||
|
||||
/**
|
||||
* A find query.
|
||||
*/
|
||||
FIND,
|
||||
|
||||
/**
|
||||
* An update query.
|
||||
*/
|
||||
UPDATE,
|
||||
|
||||
/**
|
||||
* A delete query.
|
||||
*/
|
||||
DELETE
|
||||
}
|
||||
@@ -1,16 +1,5 @@
|
||||
package io.ebean;
|
||||
|
||||
import io.ebean.util.CamelCaseHelper;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Used to build object graphs based on a raw SQL statement (rather than
|
||||
* generated by Ebean).
|
||||
@@ -169,566 +158,6 @@ import java.util.Map;
|
||||
* Note that lazy loading also works with object graphs built with RawSql.
|
||||
* </p>
|
||||
*/
|
||||
public final class RawSql implements Serializable {
|
||||
public interface RawSql {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final ResultSet resultSet;
|
||||
|
||||
private final Sql sql;
|
||||
|
||||
private final ColumnMapping columnMapping;
|
||||
|
||||
/**
|
||||
* Construct with a ResultSet and properties that the columns map to.
|
||||
* <p>
|
||||
* The properties listed in the propertyNames must be in the same order as the columns in the
|
||||
* resultSet.
|
||||
* <p>
|
||||
* When a query executes this RawSql object then it will close the resultSet.
|
||||
*/
|
||||
public RawSql(ResultSet resultSet, String... propertyNames) {
|
||||
this.resultSet = resultSet;
|
||||
this.sql = null;
|
||||
this.columnMapping = new ColumnMapping(propertyNames);
|
||||
}
|
||||
|
||||
protected RawSql(ResultSet resultSet, Sql sql, ColumnMapping columnMapping) {
|
||||
this.resultSet = resultSet;
|
||||
this.sql = sql;
|
||||
this.columnMapping = columnMapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Sql either unparsed or in parsed (broken up) form.
|
||||
*/
|
||||
public Sql getSql() {
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the key;
|
||||
*/
|
||||
public Key getKey() {
|
||||
boolean parsed = sql != null && sql.parsed;
|
||||
String unParsedSql = (sql == null) ? "" : sql.unparsedSql;
|
||||
return new Key(parsed, unParsedSql, columnMapping);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the resultSet if this is a ResultSet based RawSql.
|
||||
*/
|
||||
public ResultSet getResultSet() {
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the column mapping for the SQL columns to bean properties.
|
||||
*/
|
||||
public ColumnMapping getColumnMapping() {
|
||||
return columnMapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the sql part of the query. For parsed RawSql the sql is broken
|
||||
* up so that Ebean can insert extra WHERE and HAVING expressions into the
|
||||
* SQL.
|
||||
*/
|
||||
public static final class Sql implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final boolean parsed;
|
||||
|
||||
private final String unparsedSql;
|
||||
|
||||
private final String preFrom;
|
||||
|
||||
private final String preWhere;
|
||||
|
||||
private final boolean andWhereExpr;
|
||||
|
||||
private final String preHaving;
|
||||
|
||||
private final boolean andHavingExpr;
|
||||
|
||||
private final String orderByPrefix;
|
||||
|
||||
private final String orderBy;
|
||||
|
||||
private final boolean distinct;
|
||||
|
||||
/**
|
||||
* Construct for unparsed SQL.
|
||||
*/
|
||||
protected Sql(String unparsedSql) {
|
||||
this.parsed = false;
|
||||
this.unparsedSql = unparsedSql;
|
||||
this.preFrom = null;
|
||||
this.preHaving = null;
|
||||
this.preWhere = null;
|
||||
this.andHavingExpr = false;
|
||||
this.andWhereExpr = false;
|
||||
this.orderByPrefix = null;
|
||||
this.orderBy = null;
|
||||
this.distinct = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct for parsed SQL.
|
||||
*/
|
||||
protected Sql(String unparsedSql, String preFrom, String preWhere, boolean andWhereExpr,
|
||||
String preHaving, boolean andHavingExpr, String orderByPrefix, String orderBy, boolean distinct) {
|
||||
|
||||
this.unparsedSql = unparsedSql;
|
||||
this.parsed = true;
|
||||
this.preFrom = preFrom;
|
||||
this.preHaving = preHaving;
|
||||
this.preWhere = preWhere;
|
||||
this.andHavingExpr = andHavingExpr;
|
||||
this.andWhereExpr = andWhereExpr;
|
||||
this.orderByPrefix = orderByPrefix;
|
||||
this.orderBy = orderBy;
|
||||
this.distinct = distinct;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (!parsed) {
|
||||
return "unparsed[" + unparsedSql + "]";
|
||||
}
|
||||
return "select[" + preFrom + "] preWhere[" + preWhere + "] preHaving[" + preHaving + "] orderBy[" + orderBy + "]";
|
||||
}
|
||||
|
||||
public boolean isDistinct() {
|
||||
return distinct;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the SQL is left completely unmodified.
|
||||
* <p>
|
||||
* This means Ebean can't add WHERE or HAVING expressions into the query -
|
||||
* it will be left completely unmodified.
|
||||
* </p>
|
||||
*/
|
||||
public boolean isParsed() {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SQL when it is unparsed.
|
||||
*/
|
||||
public String getUnparsedSql() {
|
||||
return unparsedSql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SQL prior to FROM clause.
|
||||
*/
|
||||
public String getPreFrom() {
|
||||
return preFrom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SQL prior to WHERE clause.
|
||||
*/
|
||||
public String getPreWhere() {
|
||||
return preWhere;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is already a WHERE clause and any extra where
|
||||
* expressions start with AND.
|
||||
*/
|
||||
public boolean isAndWhereExpr() {
|
||||
return andWhereExpr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SQL prior to HAVING clause.
|
||||
*/
|
||||
public String getPreHaving() {
|
||||
return preHaving;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is already a HAVING clause and any extra having
|
||||
* expressions start with AND.
|
||||
*/
|
||||
public boolean isAndHavingExpr() {
|
||||
return andHavingExpr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the 'order by' keywords.
|
||||
* This can contain additional keywords, for example 'order siblings by' as Oracle syntax.
|
||||
*/
|
||||
public String getOrderByPrefix() {
|
||||
return (orderByPrefix == null) ? "order by" : orderByPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SQL ORDER BY clause.
|
||||
*/
|
||||
public String getOrderBy() {
|
||||
return orderBy;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the column mapping for raw sql DB columns to bean properties.
|
||||
*/
|
||||
public static final class ColumnMapping implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final LinkedHashMap<String, Column> dbColumnMap;
|
||||
|
||||
private final Map<String, String> propertyMap;
|
||||
|
||||
private final Map<String, Column> propertyColumnMap;
|
||||
|
||||
private final boolean parsed;
|
||||
|
||||
private final boolean immutable;
|
||||
|
||||
/**
|
||||
* Construct from parsed sql where the columns have been identified.
|
||||
*/
|
||||
protected ColumnMapping(List<Column> columns) {
|
||||
this.immutable = false;
|
||||
this.parsed = true;
|
||||
this.propertyMap = null;
|
||||
this.propertyColumnMap = null;
|
||||
this.dbColumnMap = new LinkedHashMap<>();
|
||||
for (Column c : columns) {
|
||||
dbColumnMap.put(c.getDbColumnKey(), c);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct for unparsed sql.
|
||||
*/
|
||||
protected ColumnMapping() {
|
||||
this.immutable = false;
|
||||
this.parsed = false;
|
||||
this.propertyMap = null;
|
||||
this.propertyColumnMap = null;
|
||||
this.dbColumnMap = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct for ResultSet use.
|
||||
*/
|
||||
protected ColumnMapping(String... propertyNames) {
|
||||
this.immutable = false;
|
||||
this.parsed = false;
|
||||
this.propertyMap = null;
|
||||
this.dbColumnMap = new LinkedHashMap<>();
|
||||
|
||||
int pos = 0;
|
||||
for (String prop : propertyNames) {
|
||||
dbColumnMap.put(prop, new Column(pos++, prop, null, prop));
|
||||
}
|
||||
propertyColumnMap = dbColumnMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an immutable ColumnMapping based on collected information.
|
||||
*/
|
||||
protected ColumnMapping(boolean parsed, LinkedHashMap<String, Column> dbColumnMap) {
|
||||
this.immutable = true;
|
||||
this.parsed = parsed;
|
||||
this.dbColumnMap = dbColumnMap;
|
||||
|
||||
HashMap<String, Column> pcMap = new HashMap<>();
|
||||
HashMap<String, String> pMap = new HashMap<>();
|
||||
|
||||
for (Column c : dbColumnMap.values()) {
|
||||
pMap.put(c.getPropertyName(), c.getDbColumn());
|
||||
pcMap.put(c.getPropertyName(), c);
|
||||
}
|
||||
this.propertyMap = Collections.unmodifiableMap(pMap);
|
||||
this.propertyColumnMap = Collections.unmodifiableMap(pcMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ColumnMapping that = (ColumnMapping) o;
|
||||
return dbColumnMap.equals(that.dbColumnMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return dbColumnMap.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the property is mapped.
|
||||
*/
|
||||
public boolean contains(String property) {
|
||||
return this.propertyColumnMap.containsKey(property);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an immutable copy of this ColumnMapping.
|
||||
*
|
||||
* @throws IllegalStateException when a propertyName has not been defined for a column.
|
||||
*/
|
||||
protected ColumnMapping createImmutableCopy() {
|
||||
|
||||
for (Column c : dbColumnMap.values()) {
|
||||
c.checkMapping();
|
||||
}
|
||||
|
||||
return new ColumnMapping(parsed, dbColumnMap);
|
||||
}
|
||||
|
||||
protected void columnMapping(String dbColumn, String propertyName) {
|
||||
|
||||
if (immutable) {
|
||||
throw new IllegalStateException("Should never happen");
|
||||
}
|
||||
if (!parsed) {
|
||||
int pos = dbColumnMap.size();
|
||||
dbColumnMap.put(dbColumn, new Column(pos, dbColumn, null, propertyName));
|
||||
} else {
|
||||
Column column = dbColumnMap.get(dbColumn);
|
||||
if (column == null) {
|
||||
String msg = "DB Column [" + dbColumn + "] not found in mapping. Expecting one of [" + dbColumnMap.keySet() + "]";
|
||||
throw new IllegalArgumentException(msg);
|
||||
}
|
||||
column.setPropertyName(propertyName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the Columns where supplied by parsing the sql select
|
||||
* clause.
|
||||
* <p>
|
||||
* In the case where the columns where parsed then we can do extra checks on
|
||||
* the column mapping such as, is the column a valid one in the sql and
|
||||
* whether all the columns in the sql have been mapped.
|
||||
* </p>
|
||||
*/
|
||||
public boolean isParsed() {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of columns in this column mapping.
|
||||
*/
|
||||
public int size() {
|
||||
return dbColumnMap.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the column mapping.
|
||||
*/
|
||||
protected Map<String, Column> mapping() {
|
||||
return dbColumnMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mapping by DB column.
|
||||
*/
|
||||
public Map<String, String> getMapping() {
|
||||
return propertyMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the index position by bean property name.
|
||||
*/
|
||||
public int getIndexPosition(String property) {
|
||||
Column c = propertyColumnMap.get(property);
|
||||
return c == null ? -1 : c.getIndexPos();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an iterator of the Columns.
|
||||
*/
|
||||
public Iterator<Column> getColumns() {
|
||||
return dbColumnMap.values().iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify any column mappings with the given table alias to have the path prefix.
|
||||
* <p>
|
||||
* For example modify all mappings with table alias "c" to have the path prefix "customer".
|
||||
* </p>
|
||||
* <p>
|
||||
* For the "Root type" you don't need to specify a tableAliasMapping.
|
||||
* </p>
|
||||
*/
|
||||
public void tableAliasMapping(String tableAlias, String path) {
|
||||
|
||||
String startMatch = tableAlias + ".";
|
||||
for (Map.Entry<String, Column> entry : dbColumnMap.entrySet()) {
|
||||
if (entry.getKey().startsWith(startMatch)) {
|
||||
entry.getValue().tableAliasMapping(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Column of the RawSql that is mapped to a bean property (or ignored).
|
||||
*/
|
||||
public static class Column implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final int indexPos;
|
||||
private final String dbColumn;
|
||||
|
||||
private final String dbAlias;
|
||||
|
||||
private String propertyName;
|
||||
|
||||
/**
|
||||
* Construct a Column.
|
||||
*/
|
||||
public Column(int indexPos, String dbColumn, String dbAlias) {
|
||||
this(indexPos, dbColumn, dbAlias, derivePropertyName(dbAlias, dbColumn));
|
||||
}
|
||||
|
||||
private Column(int indexPos, String dbColumn, String dbAlias, String propertyName) {
|
||||
this.indexPos = indexPos;
|
||||
this.dbColumn = dbColumn;
|
||||
this.dbAlias = dbAlias;
|
||||
if (propertyName == null && dbAlias != null) {
|
||||
this.propertyName = dbAlias;
|
||||
} else {
|
||||
this.propertyName = propertyName;
|
||||
}
|
||||
}
|
||||
|
||||
protected static String derivePropertyName(String dbAlias, String dbColumn) {
|
||||
if (dbAlias != null) {
|
||||
return CamelCaseHelper.toCamelFromUnderscore(dbAlias);
|
||||
}
|
||||
int dotPos = dbColumn.indexOf('.');
|
||||
if (dotPos > -1) {
|
||||
dbColumn = dbColumn.substring(dotPos + 1);
|
||||
}
|
||||
return CamelCaseHelper.toCamelFromUnderscore(dbColumn);
|
||||
}
|
||||
|
||||
private void checkMapping() {
|
||||
if (propertyName == null) {
|
||||
String msg = "No propertyName defined (Column mapping) for dbColumn [" + dbColumn + "]";
|
||||
throw new IllegalStateException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Column that = (Column) o;
|
||||
if (indexPos != that.indexPos) return false;
|
||||
if (!dbColumn.equals(that.dbColumn)) return false;
|
||||
if (dbAlias != null ? !dbAlias.equals(that.dbAlias) : that.dbAlias != null) return false;
|
||||
return propertyName != null ? propertyName.equals(that.propertyName) : that.propertyName == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = indexPos;
|
||||
result = 92821 * result + dbColumn.hashCode();
|
||||
result = 92821 * result + (dbAlias != null ? dbAlias.hashCode() : 0);
|
||||
result = 92821 * result + (propertyName != null ? propertyName.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return dbColumn + "->" + propertyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the index position of this column.
|
||||
*/
|
||||
public int getIndexPos() {
|
||||
return indexPos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB column alias if specified otherwise DB column.
|
||||
* This is used as the key for mapping a column to a logical property.
|
||||
*/
|
||||
public String getDbColumnKey() {
|
||||
return (dbAlias != null) ? dbAlias : dbColumn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB column name including table alias (if it has one).
|
||||
*/
|
||||
public String getDbColumn() {
|
||||
return dbColumn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean property this column is mapped to.
|
||||
*/
|
||||
public String getPropertyName() {
|
||||
return propertyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the property name mapped to this db column.
|
||||
*/
|
||||
private void setPropertyName(String propertyName) {
|
||||
this.propertyName = propertyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepend the path to the property name.
|
||||
* <p/>
|
||||
* For example if path is "customer" then "name" becomes "customer.name".
|
||||
*/
|
||||
public void tableAliasMapping(String path) {
|
||||
if (path != null) {
|
||||
propertyName = path + "." + propertyName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A key for the RawSql object using for the query plan.
|
||||
*/
|
||||
public static final class Key {
|
||||
|
||||
private final boolean parsed;
|
||||
private final ColumnMapping columnMapping;
|
||||
private final String unParsedSql;
|
||||
|
||||
Key(boolean parsed, String unParsedSql, ColumnMapping columnMapping) {
|
||||
this.parsed = parsed;
|
||||
this.unParsedSql = unParsedSql;
|
||||
this.columnMapping = columnMapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Key that = (Key) o;
|
||||
return parsed == that.parsed
|
||||
&& columnMapping.equals(that.columnMapping)
|
||||
&& unParsedSql.equals(that.unParsedSql);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = (parsed ? 1 : 0);
|
||||
result = 92821 * result + columnMapping.hashCode();
|
||||
result = 92821 * result + unParsedSql.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package io.ebean;
|
||||
|
||||
import io.ebean.RawSql.ColumnMapping;
|
||||
import io.ebean.RawSql.Sql;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Builds RawSql instances from a SQL string and column mappings.
|
||||
@@ -14,18 +12,7 @@ import java.sql.ResultSet;
|
||||
*
|
||||
* @see RawSql
|
||||
*/
|
||||
public class RawSqlBuilder {
|
||||
|
||||
/**
|
||||
* Special property name assigned to a DB column that should be ignored.
|
||||
*/
|
||||
public static final String IGNORE_COLUMN = "$$_IGNORE_COLUMN_$$";
|
||||
|
||||
private final ResultSet resultSet;
|
||||
|
||||
private final Sql sql;
|
||||
|
||||
private final ColumnMapping columnMapping;
|
||||
public interface RawSqlBuilder {
|
||||
|
||||
/**
|
||||
* Create and return a RawSql object based on the resultSet and list of properties the columns in
|
||||
@@ -34,8 +21,15 @@ public class RawSqlBuilder {
|
||||
* The properties listed in the propertyNames must be in the same order as the columns in the
|
||||
* resultSet.
|
||||
*/
|
||||
public static RawSql resultSet(ResultSet resultSet, String... propertyNames) {
|
||||
return new RawSql(resultSet, propertyNames);
|
||||
static RawSql resultSet(ResultSet resultSet, String... propertyNames) {
|
||||
return XServiceProvider.rawSql().resultSet(resultSet, propertyNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a SqlRow based on the resultSet with dbTrueValue and binaryOptimizedUUID options.
|
||||
*/
|
||||
static SqlRow sqlRow(ResultSet resultSet, final String dbTrueValue, boolean binaryOptimizedUUID) throws SQLException {
|
||||
return XServiceProvider.rawSql().sqlRow(resultSet, dbTrueValue, binaryOptimizedUUID);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,10 +37,8 @@ public class RawSqlBuilder {
|
||||
* modified - so no additional WHERE or HAVING expressions can be added to
|
||||
* this query.
|
||||
*/
|
||||
public static RawSqlBuilder unparsed(String sql) {
|
||||
|
||||
Sql s = new Sql(sql);
|
||||
return new RawSqlBuilder(s, new ColumnMapping());
|
||||
static RawSqlBuilder unparsed(String sql) {
|
||||
return XServiceProvider.rawSql().unparsed(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,19 +54,8 @@ public class RawSqlBuilder {
|
||||
* correct column names are entered into the mapping.
|
||||
* </p>
|
||||
*/
|
||||
public static RawSqlBuilder parse(String sql) {
|
||||
|
||||
Sql sql2 = DRawSqlParser.parse(sql);
|
||||
String select = sql2.getPreFrom();
|
||||
|
||||
ColumnMapping mapping = DRawSqlColumnsParser.parse(select);
|
||||
return new RawSqlBuilder(sql2, mapping);
|
||||
}
|
||||
|
||||
private RawSqlBuilder(Sql sql, ColumnMapping columnMapping) {
|
||||
this.sql = sql;
|
||||
this.columnMapping = columnMapping;
|
||||
this.resultSet = null;
|
||||
static RawSqlBuilder parse(String sql) {
|
||||
return XServiceProvider.rawSql().parsed(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,17 +68,12 @@ public class RawSqlBuilder {
|
||||
* @param dbColumn the DB column that we are mapping to a bean property
|
||||
* @param propertyName the bean property that we are mapping the DB column to.
|
||||
*/
|
||||
public RawSqlBuilder columnMapping(String dbColumn, String propertyName) {
|
||||
columnMapping.columnMapping(dbColumn, propertyName);
|
||||
return this;
|
||||
}
|
||||
RawSqlBuilder columnMapping(String dbColumn, String propertyName);
|
||||
|
||||
/**
|
||||
* Ignore this DB column. It is not mapped to any bean property.
|
||||
*/
|
||||
public RawSqlBuilder columnMappingIgnore(String dbColumn) {
|
||||
return columnMapping(dbColumn, IGNORE_COLUMN);
|
||||
}
|
||||
RawSqlBuilder columnMappingIgnore(String dbColumn);
|
||||
|
||||
/**
|
||||
* Modify any column mappings with the given table alias to have the path prefix.
|
||||
@@ -108,24 +84,12 @@ public class RawSqlBuilder {
|
||||
* For the "Root type" you don't need to specify a tableAliasMapping.
|
||||
* </p>
|
||||
*/
|
||||
public RawSqlBuilder tableAliasMapping(String tableAlias, String path) {
|
||||
columnMapping.tableAliasMapping(tableAlias, path);
|
||||
return this;
|
||||
}
|
||||
RawSqlBuilder tableAliasMapping(String tableAlias, String path);
|
||||
|
||||
/**
|
||||
* Create the immutable RawSql object. Do this after all the column mapping
|
||||
* has been defined.
|
||||
*/
|
||||
public RawSql create() {
|
||||
return new RawSql(resultSet, sql, columnMapping.createImmutableCopy());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the internal parsed Sql object (for testing).
|
||||
*/
|
||||
protected Sql getSql() {
|
||||
return sql;
|
||||
}
|
||||
RawSql create();
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Used with SqlQuery to process potentially large queries reading directly from the JDBC ResultSet.
|
||||
* <p>
|
||||
* This provides a low level option that reads directly from the JDBC ResultSet.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* String sql = "select id, name, status from o_customer order by name desc";
|
||||
*
|
||||
* Ebean.createSqlQuery(sql)
|
||||
* .findEachRow((resultSet, rowNum) -> {
|
||||
*
|
||||
* // read directly from ResultSet
|
||||
*
|
||||
* long id = resultSet.getLong(1);
|
||||
* String name = resultSet.getString(2);
|
||||
*
|
||||
* // do something interesting with the data
|
||||
*
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface RowConsumer {
|
||||
|
||||
/**
|
||||
* Read the data from the ResultSet and process it.
|
||||
*
|
||||
* @param resultSet The JDBC ResultSet positioned to the current row
|
||||
* @param rowNum The number of the current row being mapped.
|
||||
*/
|
||||
void accept(ResultSet resultSet, int rowNum) throws SQLException;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Used with SqlQuery to map raw JDBC ResultSet to objects.
|
||||
* <p>
|
||||
* This provides a low level mapping option with direct use of JDBC ResultSet
|
||||
* with the option of having logic in the mapping. For example, only map some
|
||||
* columns depending on the values read from other columns.
|
||||
* </p>
|
||||
* <p>
|
||||
* For straight mapping into beans then DtoQuery would be the first choice as
|
||||
* it can automatically map the ResultSet into beans.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* //
|
||||
* // A mapper from ResultSet into our CustomerDto bean
|
||||
* //
|
||||
* class CustomerMapper implements RowMapper<CustomerDto> {
|
||||
*
|
||||
* @Override
|
||||
* public CustomerDto map(ResultSet rset, int rowNum) throws SQLException {
|
||||
*
|
||||
* long id = rset.getLong(1);
|
||||
* String name = rset.getString(2);
|
||||
* String status = rset.getString(3);
|
||||
*
|
||||
* return new CustomerDto(id, name, status);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*
|
||||
* //
|
||||
* // Then use the mapper
|
||||
* //
|
||||
*
|
||||
* String sql = "select id, name, status from o_customer where name = ?";
|
||||
*
|
||||
* CustomerDto rob = Ebean.createSqlQuery(sql)
|
||||
* .setParameter(1, "Rob")
|
||||
* .findOne(CUSTOMER_MAPPER);
|
||||
*
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param <T> The type the row data is mapped into.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface RowMapper<T> {
|
||||
|
||||
/**
|
||||
* Read the data from the ResultSet and map to the return type.
|
||||
*
|
||||
* @param resultSet The JDBC ResultSet positioned to the current row
|
||||
* @param rowNum The number of the current row being mapped.
|
||||
*/
|
||||
T map(ResultSet resultSet, int rowNum) throws SQLException;
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
package io.ebean;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@@ -42,6 +46,7 @@ public interface SqlQuery extends Serializable {
|
||||
/**
|
||||
* Execute the query returning a list.
|
||||
*/
|
||||
@Nonnull
|
||||
List<SqlRow> findList();
|
||||
|
||||
/**
|
||||
@@ -70,7 +75,120 @@ public interface SqlQuery extends Serializable {
|
||||
* PersistenceException.
|
||||
* </p>
|
||||
*/
|
||||
SqlRow findUnique();
|
||||
@Nullable
|
||||
SqlRow findOne();
|
||||
|
||||
/**
|
||||
* Execute the query returning a single result using the mapper.
|
||||
*
|
||||
* @param mapper Used to map each ResultSet row into the result object.
|
||||
*/
|
||||
<T> T findOne(RowMapper<T> mapper);
|
||||
|
||||
/**
|
||||
* Execute the query returning a list using the mapper.
|
||||
*
|
||||
* @param mapper Used to map each ResultSet row into the result object.
|
||||
*/
|
||||
<T> List<T> findList(RowMapper<T> mapper);
|
||||
|
||||
/**
|
||||
* Execute the query reading each row from ResultSet using the RowConsumer.
|
||||
* <p>
|
||||
* This provides a low level option that reads directly from the JDBC ResultSet
|
||||
* and is good for processing very large results where (unlike findList) we don't
|
||||
* hold all the results in memory but instead can process row by row.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* String sql = "select id, name, status from customer order by name desc";
|
||||
*
|
||||
* Ebean.createSqlQuery(sql)
|
||||
* .findEachRow((resultSet, rowNum) -> {
|
||||
*
|
||||
* // read directly from ResultSet
|
||||
*
|
||||
* long id = resultSet.getLong(1);
|
||||
* String name = resultSet.getString(2);
|
||||
*
|
||||
* // do something interesting with the data
|
||||
*
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param consumer Used to read and process each ResultSet row.
|
||||
*/
|
||||
void findEachRow(RowConsumer consumer);
|
||||
|
||||
/**
|
||||
* Execute the query returning an optional row.
|
||||
*/
|
||||
@Nonnull
|
||||
Optional<SqlRow> findOneOrEmpty();
|
||||
|
||||
/**
|
||||
* Execute the query returning a single scalar attribute.
|
||||
* <pre>@{code
|
||||
*
|
||||
* String sql = "select max(unit_price) from o_order_detail where order_qty > ?";
|
||||
*
|
||||
* BigDecimal maxPrice = Ebean.createSqlQuery(sql)
|
||||
* .setParameter(1, 2)
|
||||
* .findSingleAttribute(BigDecimal.class);
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* The attributeType can be any scalar type that Ebean supports (includes javax time types, Joda types etc).
|
||||
* </p>
|
||||
*
|
||||
* @param attributeType The type of the returned value
|
||||
*/
|
||||
<T> T findSingleAttribute(Class<T> attributeType);
|
||||
|
||||
/**
|
||||
* Execute the query returning a single BigDecimal value.
|
||||
* <p>
|
||||
* This is an alias for <code>findSingleAttribute(BigDecimal.class)</code>
|
||||
* </p>
|
||||
*/
|
||||
BigDecimal findSingleDecimal();
|
||||
|
||||
/**
|
||||
* Execute the query returning a single Long value.
|
||||
* <p>
|
||||
* This is an alias for <code>findSingleAttribute(Long.class)</code>
|
||||
* </p>
|
||||
*/
|
||||
Long findSingleLong();
|
||||
|
||||
/**
|
||||
* Execute the query returning a list of scalar attribute values.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* String sql =
|
||||
* " select (unit_price * order_qty) " +
|
||||
* " from o_order_detail " +
|
||||
* " where unit_price > ? " +
|
||||
* " order by (unit_price * order_qty) desc";
|
||||
*
|
||||
* //
|
||||
* List<BigDecimal> lineAmounts = Ebean.createSqlQuery(sql)
|
||||
* .setParameter(1, 3)
|
||||
* .findSingleAttributeList(BigDecimal.class);
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* The attributeType can be any scalar type that Ebean supports (includes javax time types, Joda types etc).
|
||||
* </p>
|
||||
*
|
||||
* @param attributeType The type of the returned value
|
||||
*/
|
||||
<T> List<T> findSingleAttributeList(Class<T> attributeType);
|
||||
|
||||
/**
|
||||
* The same as bind for named parameters.
|
||||
@@ -104,6 +222,11 @@ public interface SqlQuery extends Serializable {
|
||||
*/
|
||||
SqlQuery setTimeout(int secs);
|
||||
|
||||
/**
|
||||
* Set a label that can be put on performance metrics that are collected.
|
||||
*/
|
||||
SqlQuery setLabel(String label);
|
||||
|
||||
/**
|
||||
* A hint which for JDBC translates to the Statement.fetchSize().
|
||||
* <p>
|
||||
|
||||
@@ -19,15 +19,73 @@ package io.ebean;
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // example that uses 'named' parameters
|
||||
* String s = "UPDATE f_topic set post_count = :count where id = :id";
|
||||
* SqlUpdate update = Ebean.createSqlUpdate(s);
|
||||
* update.setParameter("id", 1);
|
||||
* update.setParameter("count", 50);
|
||||
* // example that uses 'named' parameters
|
||||
*
|
||||
* int modifiedCount = Ebean.execute(update);
|
||||
* String s = "UPDATE f_topic set post_count = :count where id = :id";
|
||||
*
|
||||
* String msg = "There were " + modifiedCount + " rows updated";
|
||||
* SqlUpdate update = Ebean.createSqlUpdate(s);
|
||||
* update.setParameter("id", 1);
|
||||
* update.setParameter("count", 50);
|
||||
*
|
||||
* int modifiedCount = update.execute();
|
||||
*
|
||||
* String msg = "There were " + modifiedCount + " rows updated";
|
||||
*
|
||||
* }</pre>
|
||||
* <p>
|
||||
* <h3>Example: Using setNextParameter()</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* String sql = "insert into audit_log (id, description, modified_description) values (?,?,?)";
|
||||
* SqlUpdate insert = Ebean.createSqlUpdate(sql);
|
||||
*
|
||||
* try (Transaction txn = Ebean.beginTransaction()) {
|
||||
* txn.setBatchMode(true);
|
||||
*
|
||||
* insert.setNextParameter(10000);
|
||||
* insert.setNextParameter("hello");
|
||||
* insert.setNextParameter("rob");
|
||||
* insert.execute();
|
||||
*
|
||||
* insert.setNextParameter(10001);
|
||||
* insert.setNextParameter("goodbye");
|
||||
* insert.setNextParameter("rob");
|
||||
* insert.execute();
|
||||
*
|
||||
* insert.setNextParameter(10002);
|
||||
* insert.setNextParameter("chow");
|
||||
* insert.setNextParameter("bob");
|
||||
* insert.execute();
|
||||
*
|
||||
* txn.commit();
|
||||
* }
|
||||
* }</pre>
|
||||
* <p>
|
||||
* An alternative to the batch mode on the transaction is to use addBatch() and executeBatch() like:
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* try (Transaction txn = Ebean.beginTransaction()) {
|
||||
*
|
||||
* insert.setNextParameter(10000);
|
||||
* insert.setNextParameter("hello");
|
||||
* insert.setNextParameter("rob");
|
||||
* insert.addBatch();
|
||||
*
|
||||
* insert.setNextParameter(10001);
|
||||
* insert.setNextParameter("goodbye");
|
||||
* insert.setNextParameter("rob");
|
||||
* insert.addBatch();
|
||||
*
|
||||
* insert.setNextParameter(10002);
|
||||
* insert.setNextParameter("chow");
|
||||
* insert.setNextParameter("bob");
|
||||
* insert.addBatch();
|
||||
*
|
||||
* int[] rows = insert.executeBatch();
|
||||
*
|
||||
* txn.commit();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
@@ -54,6 +112,37 @@ public interface SqlUpdate {
|
||||
*/
|
||||
int execute();
|
||||
|
||||
/**
|
||||
* Execute when addBatch() has been used to batch multiple bind executions.
|
||||
*
|
||||
* @return The row counts for each of the batched statements.
|
||||
*/
|
||||
int[] executeBatch();
|
||||
|
||||
/**
|
||||
* Add the statement to batch processing to then later execute via executeBatch().
|
||||
*/
|
||||
void addBatch();
|
||||
|
||||
/**
|
||||
* Return the generated key value.
|
||||
*/
|
||||
Object getGeneratedKey();
|
||||
|
||||
/**
|
||||
* Execute and return the generated key. This is effectively a short cut for:
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* sqlUpdate.execute();
|
||||
* Object key = sqlUpdate.getGeneratedKey();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return The generated key value
|
||||
*/
|
||||
Object executeGetKey();
|
||||
|
||||
/**
|
||||
* Return true if eBean should automatically deduce the table modification
|
||||
* information and process it.
|
||||
@@ -88,6 +177,11 @@ public interface SqlUpdate {
|
||||
*/
|
||||
SqlUpdate setLabel(String label);
|
||||
|
||||
/**
|
||||
* Set to true when we want to use getGeneratedKeys with this statement.
|
||||
*/
|
||||
SqlUpdate setGetGeneratedKeys(boolean getGeneratedKeys);
|
||||
|
||||
/**
|
||||
* Return the sql statement.
|
||||
*/
|
||||
@@ -113,6 +207,13 @@ public interface SqlUpdate {
|
||||
*/
|
||||
SqlUpdate setTimeout(int secs);
|
||||
|
||||
/**
|
||||
* Set the next positioned parameter.
|
||||
*
|
||||
* @param value The value to bind
|
||||
*/
|
||||
SqlUpdate setNextParameter(Object value);
|
||||
|
||||
/**
|
||||
* Set a parameter via its index position.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.ebean;
|
||||
|
||||
import io.ebean.annotation.DocStoreMode;
|
||||
import io.ebean.annotation.PersistBatch;
|
||||
import io.ebean.config.DocStoreConfig;
|
||||
import io.ebean.config.ServerConfig;
|
||||
|
||||
@@ -12,6 +13,24 @@ import java.sql.Connection;
|
||||
*/
|
||||
public interface Transaction extends AutoCloseable {
|
||||
|
||||
/**
|
||||
* Return the current transaction (of the default server) or null if there is
|
||||
* no current transaction in scope.
|
||||
* <p>
|
||||
* This is the same as <code>Ebean.currentTransaction()</code>
|
||||
* </p>
|
||||
* <p>
|
||||
* This returns the current transaction for the 'default server'. If you are using
|
||||
* multiple EbeanServer's then use {@link EbeanServer#currentTransaction()}.
|
||||
* </p>
|
||||
*
|
||||
* @see Ebean#currentTransaction()
|
||||
* @see EbeanServer#currentTransaction()
|
||||
*/
|
||||
static Transaction current() {
|
||||
return Ebean.currentTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read Committed transaction isolation. Same as
|
||||
* java.sql.Connection.TRANSACTION_READ_COMMITTED.
|
||||
@@ -41,6 +60,14 @@ public interface Transaction extends AutoCloseable {
|
||||
*/
|
||||
void register(TransactionCallback callback);
|
||||
|
||||
/**
|
||||
* Set a label on the transaction.
|
||||
* <p>
|
||||
* This label is used to group transaction execution times for performance metrics reporting.
|
||||
* </p>
|
||||
*/
|
||||
void setLabel(String label);
|
||||
|
||||
/**
|
||||
* Return true if this transaction is read only.
|
||||
*/
|
||||
@@ -110,6 +137,18 @@ public interface Transaction extends AutoCloseable {
|
||||
*/
|
||||
void rollback(Throwable e) throws PersistenceException;
|
||||
|
||||
/**
|
||||
* Set when we want nested transactions to use Savepoint's.
|
||||
* <p>
|
||||
* This means that for a nested transaction:
|
||||
* <ul>
|
||||
* <li>begin transaction maps to creating a savepoint</li>
|
||||
* <li>commit transaction maps to releasing a savepoint</li>
|
||||
* <li>rollback transaction maps to rollback a savepoint</li>
|
||||
* </ul>
|
||||
*/
|
||||
void setNestedUseSavepoint();
|
||||
|
||||
/**
|
||||
* Mark the transaction for rollback only.
|
||||
*/
|
||||
@@ -125,10 +164,10 @@ public interface Transaction extends AutoCloseable {
|
||||
*/
|
||||
void end();
|
||||
|
||||
|
||||
/**
|
||||
* Synonym for end() to support AutoClosable.
|
||||
*/
|
||||
@Override
|
||||
void close();
|
||||
|
||||
/**
|
||||
@@ -200,7 +239,7 @@ public interface Transaction extends AutoCloseable {
|
||||
* Refer to {@link ServerConfig#setSkipCacheAfterWrite(boolean)} for configuring the default behavior
|
||||
* for using the L2 bean cache in transactions spanning multiple query/persist requests.
|
||||
* </p>
|
||||
* <p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* // assume Customer has L2 bean caching enabled ...
|
||||
@@ -282,7 +321,7 @@ public interface Transaction extends AutoCloseable {
|
||||
* <p>
|
||||
* Example: batch processing executing every 3 rows
|
||||
* </p>
|
||||
* <p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* String data = "This is a simple test of the batch processing"
|
||||
@@ -320,7 +359,10 @@ public interface Transaction extends AutoCloseable {
|
||||
void setBatchMode(boolean useBatch);
|
||||
|
||||
/**
|
||||
* The JDBC batch mode to use for this transaction.
|
||||
* Deprecated - migrate to {@link #setBatchMode(boolean)}.
|
||||
* <p>
|
||||
* Set the JDBC batch mode to use for this transaction.
|
||||
* </p>
|
||||
* <p>
|
||||
* If this is NONE then JDBC batch can still be used for each request - save(), insert(), update() or delete()
|
||||
* and this would be useful if the request cascades to detail beans.
|
||||
@@ -329,15 +371,23 @@ public interface Transaction extends AutoCloseable {
|
||||
* @param persistBatchMode the batch mode to use for this transaction
|
||||
* @see io.ebean.config.ServerConfig#setPersistBatch(PersistBatch)
|
||||
*/
|
||||
@Deprecated
|
||||
void setBatch(PersistBatch persistBatchMode);
|
||||
|
||||
/**
|
||||
* Deprecated - migrate to {@link #isBatchMode()}.
|
||||
* Return the batch mode at the transaction level.
|
||||
*/
|
||||
@Deprecated
|
||||
PersistBatch getBatch();
|
||||
|
||||
/**
|
||||
* Return the batch mode at the transaction level.
|
||||
*/
|
||||
PersistBatch getBatch();
|
||||
boolean isBatchMode();
|
||||
|
||||
/**
|
||||
* Set the JDBC batch mode to use for a save() or delete() request.
|
||||
* Set the JDBC batch mode to use for a save() or delete() when cascading to children.
|
||||
* <p>
|
||||
* This only takes effect when batch mode on the transaction has not already meant that
|
||||
* JDBC batch mode is being used.
|
||||
@@ -346,17 +396,36 @@ public interface Transaction extends AutoCloseable {
|
||||
* This is useful when the single save() or delete() cascades. For example, inserting a 'master' cascades
|
||||
* and inserts a collection of 'detail' beans. The detail beans can be inserted using JDBC batch.
|
||||
* </p>
|
||||
* <p>
|
||||
* This is effectively already turned on for all platforms apart from older Sql Server.
|
||||
* </p>
|
||||
*
|
||||
* @param batchOnCascadeMode the batch mode to use per save(), insert(), update() or delete()
|
||||
* @param batchMode the batch mode to use per save(), insert(), update() or delete()
|
||||
* @see io.ebean.config.ServerConfig#setPersistBatchOnCascade(PersistBatch)
|
||||
*/
|
||||
void setBatchOnCascade(boolean batchMode);
|
||||
|
||||
/**
|
||||
* Set the batch mode when cascading.
|
||||
* <p>
|
||||
* Deprecated in favour of {@link #setBatchOnCascade(boolean)}
|
||||
* </p>
|
||||
*/
|
||||
@Deprecated
|
||||
void setBatchOnCascade(PersistBatch batchOnCascadeMode);
|
||||
|
||||
/**
|
||||
* Deprecated - migrate to {@link #isBatchMode()}.
|
||||
* Return the batch mode at the request level (for each save(), insert(), update() or delete()).
|
||||
*/
|
||||
@Deprecated
|
||||
PersistBatch getBatchOnCascade();
|
||||
|
||||
/**
|
||||
* Return the batch mode at the request level.
|
||||
*/
|
||||
boolean isBatchOnCascade();
|
||||
|
||||
/**
|
||||
* Specify the number of statements before a batch is flushed automatically.
|
||||
*/
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
package io.ebean;
|
||||
|
||||
/**
|
||||
* Execute a TxCallable in a Transaction scope.
|
||||
* <p>
|
||||
* Use this with the {@link Ebean#execute(TxCallable)} method.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that this is basically the same as TxRunnable except that it returns an
|
||||
* Object (and you specify the return type via generics).
|
||||
* </p>
|
||||
* <p>
|
||||
* See also {@link TxRunnable}.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* Ebean.execute(new TxCallable<String>() {
|
||||
* public String call() {
|
||||
* User u1 = Ebean.find(User.class, 1);
|
||||
* User u2 = Ebean.find(User.class, 2);
|
||||
*
|
||||
* u1.setName("u1 mod");
|
||||
* u2.setName("u2 mod");
|
||||
*
|
||||
* Ebean.save(u1);
|
||||
* Ebean.save(u2);
|
||||
*
|
||||
* return u1.getEmail();
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see TxRunnable
|
||||
*/
|
||||
public interface TxCallable<T> {
|
||||
|
||||
/**
|
||||
* Execute the method within a transaction scope returning the result.
|
||||
* <p>
|
||||
* If you do not want to return a result you should look to use TxRunnable
|
||||
* instead.
|
||||
* </p>
|
||||
*/
|
||||
T call();
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package io.ebean;
|
||||
|
||||
/**
|
||||
* Execute a TxRunnable in a Transaction scope.
|
||||
* <p>
|
||||
* Use this with the {@link Ebean#execute(TxRunnable)} method.
|
||||
* </p>
|
||||
* <p>
|
||||
* See also {@link TxCallable}.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // this run method runs in a transaction scope
|
||||
* // which by default is TxScope.REQUIRED
|
||||
*
|
||||
* Ebean.execute(new TxRunnable() {
|
||||
* public void run() {
|
||||
* User u1 = Ebean.find(User.class, 1);
|
||||
* User u2 = Ebean.find(User.class, 2);
|
||||
*
|
||||
* u1.setName("u1 mod");
|
||||
* u2.setName("u2 mod");
|
||||
*
|
||||
* Ebean.save(u1);
|
||||
* Ebean.save(u2);
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see TxCallable
|
||||
*/
|
||||
public interface TxRunnable {
|
||||
|
||||
/**
|
||||
* Run the method in a transaction sope.
|
||||
*/
|
||||
void run();
|
||||
}
|
||||
@@ -1,46 +1,62 @@
|
||||
package io.ebean;
|
||||
|
||||
import io.ebean.annotation.PersistBatch;
|
||||
import io.ebean.annotation.TxIsolation;
|
||||
import io.ebean.annotation.TxType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* Holds the definition of how a transactional method should run.
|
||||
* <p>
|
||||
* This information matches the features of the Transactional annotation. You
|
||||
* can use it directly with TxRunnable or TxCallable via
|
||||
* {@link Ebean#execute(TxScope, TxCallable)} or
|
||||
* {@link Ebean#execute(TxScope, TxRunnable)}.
|
||||
* can use it directly with Runnable or Callable via
|
||||
* {@link Ebean#execute(TxScope, Runnable)} or
|
||||
* {@link Ebean#executeCall(TxScope, Callable)}.
|
||||
* </p>
|
||||
* <p>
|
||||
* This object is used internally with the enhancement of a method with
|
||||
* Transactional annotation.
|
||||
* </p>
|
||||
*
|
||||
* @see TxCallable
|
||||
* @see TxRunnable
|
||||
* @see Ebean#execute(TxScope, TxCallable)
|
||||
* @see Ebean#execute(TxScope, TxRunnable)
|
||||
* @see Ebean#execute(TxScope, Runnable)
|
||||
* @see Ebean#executeCall(TxScope, Callable)
|
||||
*/
|
||||
public final class TxScope {
|
||||
|
||||
TxType type;
|
||||
private int profileId;
|
||||
|
||||
String serverName;
|
||||
private TxType type;
|
||||
|
||||
TxIsolation isolation;
|
||||
private String serverName;
|
||||
|
||||
PersistBatch batch;
|
||||
private TxIsolation isolation;
|
||||
|
||||
PersistBatch batchOnCascade;
|
||||
private PersistBatch batch;
|
||||
|
||||
int batchSize;
|
||||
private PersistBatch batchOnCascade;
|
||||
|
||||
boolean skipGeneratedKeys;
|
||||
private int batchSize;
|
||||
|
||||
boolean readOnly;
|
||||
private boolean skipGeneratedKeys;
|
||||
|
||||
ArrayList<Class<? extends Throwable>> rollbackFor;
|
||||
private boolean readOnly;
|
||||
|
||||
ArrayList<Class<? extends Throwable>> noRollbackFor;
|
||||
/**
|
||||
* Set this to false if the JDBC batch should not be automatically be flushed when a query is executed.
|
||||
*/
|
||||
private boolean flushOnQuery = true;
|
||||
|
||||
private boolean skipCache;
|
||||
|
||||
private String label;
|
||||
|
||||
private ArrayList<Class<? extends Throwable>> rollbackFor;
|
||||
|
||||
private ArrayList<Class<? extends Throwable>> noRollbackFor;
|
||||
|
||||
private ProfileLocation profileLocation;
|
||||
|
||||
/**
|
||||
* Helper method to create a TxScope with REQUIRES.
|
||||
@@ -160,6 +176,66 @@ public final class TxScope {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the transaction profile id.
|
||||
*/
|
||||
public int getProfileId() {
|
||||
return profileId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the transaction profile id.
|
||||
*/
|
||||
public TxScope setProfileId(int profileId) {
|
||||
this.profileId = profileId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the profile location.
|
||||
*/
|
||||
public ProfileLocation getProfileLocation() {
|
||||
return profileLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the profile location.
|
||||
*/
|
||||
public TxScope setProfileLocation(ProfileLocation profileLocation) {
|
||||
this.profileLocation = profileLocation;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the L2 cache should be skipped for this transaction.
|
||||
*/
|
||||
public boolean isSkipCache() {
|
||||
return skipCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if the transaction should skip L2 cache access.
|
||||
*/
|
||||
public TxScope setSkipCache(boolean skipCache) {
|
||||
this.skipCache = skipCache;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the label for the transaction.
|
||||
*/
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a label for the transaction.
|
||||
*/
|
||||
public TxScope setLabel(String label) {
|
||||
this.label = label;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the batch mode.
|
||||
*/
|
||||
@@ -235,6 +311,28 @@ public final class TxScope {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return false if the JDBC batch buffer should not be flushed automatically when a query is executed.
|
||||
*/
|
||||
public boolean isFlushOnQuery() {
|
||||
return flushOnQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set flushOnQuery to be false to stop automatically flushing the JDBC batch buffer when a query is executed.
|
||||
*/
|
||||
public TxScope setFlushOnQuery(boolean flushOnQuery) {
|
||||
this.flushOnQuery = flushOnQuery;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the isolation level.
|
||||
*/
|
||||
public int getIsolationLevel() {
|
||||
return isolation != null ? isolation.getLevel() : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Isolation level this transaction should run with.
|
||||
*/
|
||||
|
||||
@@ -152,6 +152,11 @@ public interface Update<T> {
|
||||
*/
|
||||
Update<T> setNullParameter(String name, int jdbcType);
|
||||
|
||||
/**
|
||||
* Set a label meaning performance metrics will be collected for the execution of this update.
|
||||
*/
|
||||
Update<T> setLabel(String label);
|
||||
|
||||
/**
|
||||
* Return the sql that is actually executed.
|
||||
*/
|
||||
|
||||
@@ -148,6 +148,12 @@ public interface UpdateQuery<T> {
|
||||
*/
|
||||
UpdateQuery<T> setRaw(String propertyExpression, Object... values);
|
||||
|
||||
/**
|
||||
* Set the profile location of this update query. This is used to relate query execution metrics
|
||||
* back to a location like a specific line of code.
|
||||
*/
|
||||
UpdateQuery<T> setProfileLocation(ProfileLocation profileLocation);
|
||||
|
||||
/**
|
||||
* Return the query expression list to add predicates to.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package io.ebean;
|
||||
|
||||
import io.ebean.service.SpiProfileLocationFactory;
|
||||
import io.ebean.service.SpiRawSqlService;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
/**
|
||||
* Lookup internal services.
|
||||
*/
|
||||
class XServiceProvider {
|
||||
|
||||
private static SpiRawSqlService rawSqlService = initRawSql();
|
||||
|
||||
private static SpiProfileLocationFactory profileLocationFactory = initProfileLocation();
|
||||
|
||||
private static SpiRawSqlService initRawSql() {
|
||||
|
||||
Iterator<SpiRawSqlService> loader = ServiceLoader.load(SpiRawSqlService.class).iterator();
|
||||
if (loader.hasNext()) {
|
||||
return loader.next();
|
||||
}
|
||||
throw new IllegalStateException("No service implementation found for SpiRawSqlService?");
|
||||
}
|
||||
|
||||
private static SpiProfileLocationFactory initProfileLocation() {
|
||||
|
||||
Iterator<SpiProfileLocationFactory> loader = ServiceLoader.load(SpiProfileLocationFactory.class).iterator();
|
||||
if (loader.hasNext()) {
|
||||
return loader.next();
|
||||
}
|
||||
throw new IllegalStateException("No service implementation found for SpiProfileLocationFactory?");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the RawSqlService implementation.
|
||||
*/
|
||||
static SpiRawSqlService rawSql() {
|
||||
return rawSqlService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the RawSqlService implementation.
|
||||
*/
|
||||
static SpiProfileLocationFactory profileLocationFactory() {
|
||||
return profileLocationFactory;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
package io.ebean.annotation;
|
||||
|
||||
import io.ebean.TxIsolation;
|
||||
import io.ebean.TxType;
|
||||
import io.ebean.PersistBatch;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Specify transaction scoping for a method.
|
||||
* <p>
|
||||
* <b><i> This is only supported if "Enhancement" is used via javaagent, ANT
|
||||
* task or IDE enhancement plugin etc. </i></b>
|
||||
* </p>
|
||||
* <p>
|
||||
* Note: Currently there are 3 known annotations that perform this role.
|
||||
* <ul>
|
||||
* <li>EJB's javax.ejb.TransactionAttribute</li>
|
||||
* <li>Spring's org.springframework.transaction.annotation.Transactional</li>
|
||||
* <li>and this one, Ebean's own Transactional</li>
|
||||
* </ul>
|
||||
* Spring created their one because the EJB annotation does not support features
|
||||
* such as isolation level and specifying rollbackOn, noRollbackOn exceptions.
|
||||
* This one exists for Ebean because I agree that the standard one is
|
||||
* insufficient and don't want to include a dependency on Spring.
|
||||
* </p>
|
||||
* <p>
|
||||
* The default behaviour of EJB (and hence Spring) is to NOT ROLLBACK on checked
|
||||
* exceptions. I find this very counter-intuitive. Ebean will provide a property
|
||||
* to set the default behaviour to rollback on any exception and optionally
|
||||
* change the setting to be consistent with EJB/Spring if people wish to do so.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // a normal class
|
||||
* public class MySimpleUserService {
|
||||
*
|
||||
* // this method is transactional automatically handling
|
||||
* // transaction begin, commit and rollback etc
|
||||
* @Transactional
|
||||
* public void runInTrans() throws IOException {
|
||||
*
|
||||
* // tasks performed within the transaction
|
||||
* ...
|
||||
* // find some objects
|
||||
* Customer cust = ebeanServer.find(Customer.class, 42);
|
||||
*
|
||||
* Order order = ...;
|
||||
* ...
|
||||
* // save some objects
|
||||
* ebeanServer.save(customer);
|
||||
* ebeanServer.save(order);
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Transactional {
|
||||
|
||||
/**
|
||||
* The type of transaction scoping. Defaults to REQUIRED.
|
||||
*/
|
||||
TxType type() default TxType.REQUIRED;
|
||||
|
||||
/**
|
||||
* Persist batch mode for the transaction.
|
||||
*/
|
||||
PersistBatch batch() default PersistBatch.INHERIT;
|
||||
|
||||
/**
|
||||
* Persist batch mode for the request if not set on the transaction.
|
||||
* <p>
|
||||
* If batch is set to NONE then batchOnCascade can be set to INSERT or ALL
|
||||
* and then each save(), delete(), insert(), update() request that cascades
|
||||
* to child beans can use JDBC batch.
|
||||
* </p>
|
||||
*/
|
||||
PersistBatch batchOnCascade() default PersistBatch.INHERIT;
|
||||
|
||||
/**
|
||||
* The batch size to use when using JDBC batch mode.
|
||||
* <p>
|
||||
* If unset this defaults to the value set in ServerConfig.
|
||||
* </p>
|
||||
*/
|
||||
int batchSize() default 0;
|
||||
|
||||
/**
|
||||
* Set to false when we want to skip getting generatedKeys.
|
||||
* <p>
|
||||
* This is typically used in the case of large batch inserts where we get a
|
||||
* performance benefit from not calling getGeneratedKeys (as we are going to
|
||||
* insert a lot of rows and have no need for the Id values after the insert).
|
||||
* </p>
|
||||
*/
|
||||
boolean getGeneratedKeys() default true;
|
||||
|
||||
/**
|
||||
* The transaction isolation level this transaction should have.
|
||||
* <p>
|
||||
* This will only be used if this scope creates the transaction. If the
|
||||
* transaction has already started then this will currently be ignored (you
|
||||
* could argue that it should throw an exception).
|
||||
* </p>
|
||||
*/
|
||||
TxIsolation isolation() default TxIsolation.DEFAULT;
|
||||
|
||||
/**
|
||||
* Set this to true if the transaction should be only contain queries.
|
||||
*/
|
||||
boolean readOnly() default false;
|
||||
|
||||
/**
|
||||
* The name of the server that you want the transaction to be created from.
|
||||
* <p>
|
||||
* If left blank the 'default' server is used.
|
||||
* </p>
|
||||
*/
|
||||
String serverName() default "";
|
||||
|
||||
// int timeout() default 0;
|
||||
|
||||
/**
|
||||
* The Throwable's that will explicitly cause a rollback to occur.
|
||||
*/
|
||||
Class<? extends Throwable>[] rollbackFor() default {};
|
||||
|
||||
/**
|
||||
* The Throwable's that will explicitly NOT cause a rollback to occur.
|
||||
*/
|
||||
Class<? extends Throwable>[] noRollbackFor() default {};
|
||||
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* Transactional annotations.
|
||||
*/
|
||||
package io.ebean.annotation;
|
||||
@@ -72,6 +72,11 @@ public interface BeanCollection<E> extends Serializable {
|
||||
*/
|
||||
boolean isSkipSave();
|
||||
|
||||
/**
|
||||
* Return true if the collection holds modifications.
|
||||
*/
|
||||
boolean holdsModifications();
|
||||
|
||||
/**
|
||||
* Return the bean that owns this collection.
|
||||
*/
|
||||
@@ -191,6 +196,11 @@ public interface BeanCollection<E> extends Serializable {
|
||||
*/
|
||||
void setModifyListening(ModifyListenMode modifyListenMode);
|
||||
|
||||
/**
|
||||
* Return the current modify listening mode. Can be null for on newly created beans.
|
||||
*/
|
||||
ModifyListenMode getModifyListening();
|
||||
|
||||
/**
|
||||
* Add an object to the additions list.
|
||||
* <p>
|
||||
@@ -226,4 +236,14 @@ public interface BeanCollection<E> extends Serializable {
|
||||
* additions and removals have been processed.
|
||||
*/
|
||||
void modifyReset();
|
||||
|
||||
/**
|
||||
* Has been modified by an addition or removal.
|
||||
*/
|
||||
boolean wasTouched();
|
||||
|
||||
/**
|
||||
* Return a shallow copy of this collection that is modifiable.
|
||||
*/
|
||||
BeanCollection<E> getShallowCopy();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package io.ebean.bean;
|
||||
|
||||
/**
|
||||
* Visitor for collecting new/old values for a bean update.
|
||||
*/
|
||||
public interface BeanDiffVisitor {
|
||||
|
||||
/**
|
||||
* Collect a new/old value pair.
|
||||
*/
|
||||
void visit(int position, Object newVal, Object oldVal);
|
||||
|
||||
/**
|
||||
* Start processing an associated bean.
|
||||
*/
|
||||
void visitPush(int position);
|
||||
|
||||
/**
|
||||
* Stop processing an associated bean.
|
||||
*/
|
||||
void visitPop();
|
||||
}
|
||||
@@ -26,14 +26,16 @@ public final class CallStack implements Serializable {
|
||||
|
||||
private final StackTraceElement[] callStack;
|
||||
|
||||
private final int hc;
|
||||
|
||||
public CallStack(StackTraceElement[] callStack, int zeroHash, int pathHash) {
|
||||
this.callStack = callStack;
|
||||
this.zeroHash = enc(zeroHash);
|
||||
this.pathHash = enc(pathHash);
|
||||
this.hc = computeHashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
private int computeHashCode() {
|
||||
int hc = 0;
|
||||
for (StackTraceElement aCallStack : callStack) {
|
||||
hc = 92821 * hc + aCallStack.hashCode();
|
||||
@@ -41,6 +43,11 @@ public final class CallStack implements Serializable {
|
||||
return hc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package io.ebean.bean;
|
||||
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
@@ -14,8 +13,14 @@ import java.io.Serializable;
|
||||
*/
|
||||
public interface EntityBean extends Serializable {
|
||||
|
||||
/**
|
||||
* Return all the property names in defined order.
|
||||
*/
|
||||
String[] _ebean_getPropertyNames();
|
||||
|
||||
/**
|
||||
* Return the property name at the given position.
|
||||
*/
|
||||
String _ebean_getPropertyName(int pos);
|
||||
|
||||
/**
|
||||
@@ -32,16 +37,6 @@ public interface EntityBean extends Serializable {
|
||||
*/
|
||||
Object _ebean_newInstance();
|
||||
|
||||
/**
|
||||
* Add a PropertyChangeListener to this bean.
|
||||
*/
|
||||
void addPropertyChangeListener(PropertyChangeListener listener);
|
||||
|
||||
/**
|
||||
* Remove a PropertyChangeListener from this bean.
|
||||
*/
|
||||
void removePropertyChangeListener(PropertyChangeListener listener);
|
||||
|
||||
/**
|
||||
* Generated method that sets the loaded state on all the embedded beans on
|
||||
* this entity bean by using EntityBeanIntercept.setEmbeddedLoaded(Object o);
|
||||
|
||||
@@ -5,12 +5,10 @@ import io.ebean.ValuePair;
|
||||
|
||||
import javax.persistence.EntityNotFoundException;
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.beans.PropertyChangeSupport;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
@@ -33,8 +31,6 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
|
||||
private transient NodeUsageCollector nodeUsageCollector;
|
||||
|
||||
private transient PropertyChangeSupport pcs;
|
||||
|
||||
private transient PersistenceContext persistenceContext;
|
||||
|
||||
private transient BeanLoader beanLoader;
|
||||
@@ -56,6 +52,8 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
*/
|
||||
private int state;
|
||||
|
||||
private boolean forceUpdate;
|
||||
|
||||
private boolean readOnly;
|
||||
|
||||
private boolean dirty;
|
||||
@@ -94,6 +92,7 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
private int lazyLoadProperty = -1;
|
||||
|
||||
private Object ownerId;
|
||||
private int sortOrder;
|
||||
|
||||
/**
|
||||
* Create a intercept with a given entity.
|
||||
@@ -127,46 +126,6 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
this.persistenceContext = persistenceContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a property change listener for this entity bean.
|
||||
*/
|
||||
public void addPropertyChangeListener(PropertyChangeListener listener) {
|
||||
if (pcs == null) {
|
||||
pcs = new PropertyChangeSupport(owner);
|
||||
}
|
||||
pcs.addPropertyChangeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a property change listener for this entity bean for a specific
|
||||
* property.
|
||||
*/
|
||||
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
|
||||
if (pcs == null) {
|
||||
pcs = new PropertyChangeSupport(owner);
|
||||
}
|
||||
pcs.addPropertyChangeListener(propertyName, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a property change listener for this entity bean.
|
||||
*/
|
||||
public void removePropertyChangeListener(PropertyChangeListener listener) {
|
||||
if (pcs != null) {
|
||||
pcs.removePropertyChangeListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a property change listener for this entity bean for a specific
|
||||
* property.
|
||||
*/
|
||||
public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
|
||||
if (pcs != null) {
|
||||
pcs.removePropertyChangeListener(propertyName, listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn on profile collection.
|
||||
*/
|
||||
@@ -174,6 +133,20 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
this.nodeUsageCollector = usageCollector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ownerId (IdClass).
|
||||
*/
|
||||
public Object getOwnerId() {
|
||||
return ownerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ownerId (IdClass).
|
||||
*/
|
||||
public void setOwnerId(Object ownerId) {
|
||||
this.ownerId = ownerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the owning bean for an embedded bean.
|
||||
*/
|
||||
@@ -335,6 +308,20 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
this.readOnly = readOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bean to be updated when persisted (for merge).
|
||||
*/
|
||||
public void setForceUpdate(boolean forceUpdate) {
|
||||
this.forceUpdate = forceUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the entity should be updated.
|
||||
*/
|
||||
public boolean isUpdate() {
|
||||
return forceUpdate || state == STATE_LOADED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the entity has been loaded.
|
||||
*/
|
||||
@@ -506,6 +493,15 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
loadedProps[propertyIndex] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all properties to be loaded (post insert).
|
||||
*/
|
||||
public void setLoadedPropertyAll() {
|
||||
for (int i = 0; i < loadedProps.length; i++) {
|
||||
loadedProps[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the property is loaded.
|
||||
*/
|
||||
@@ -563,6 +559,16 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set old value but force it to be set regardless if it already has a value.
|
||||
*/
|
||||
private void setOriginalValueForce(int propertyIndex, Object value) {
|
||||
if (origValues == null) {
|
||||
origValues = new Object[owner._ebean_getPropertyNames().length];
|
||||
}
|
||||
origValues[propertyIndex] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* For forced update on a 'New' bean set all the loaded properties to changed.
|
||||
*/
|
||||
@@ -693,6 +699,28 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively add dirty properties.
|
||||
*/
|
||||
public void addDirtyPropertyValues(BeanDiffVisitor visitor) {
|
||||
int len = getPropertyLength();
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (changedProps != null && changedProps[i]) {
|
||||
// the property has been changed on this bean
|
||||
Object newVal = owner._ebean_getField(i);
|
||||
Object oldVal = getOrigValue(i);
|
||||
visitor.visit(i, newVal, oldVal);
|
||||
|
||||
} else if (embeddedDirty != null && embeddedDirty[i]) {
|
||||
// an embedded property has been changed - recurse
|
||||
EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
|
||||
visitor.visitPush(i);
|
||||
embeddedBean._ebean_getIntercept().addDirtyPropertyValues(visitor);
|
||||
visitor.visitPop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a dirty property hash taking into account embedded beans.
|
||||
*/
|
||||
@@ -706,6 +734,9 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
* Add and return a dirty property hash recursing into embedded beans.
|
||||
*/
|
||||
private void addDirtyPropertyKey(StringBuilder sb) {
|
||||
if (sortOrder > 0) {
|
||||
sb.append("s,");
|
||||
}
|
||||
int len = getPropertyLength();
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (changedProps != null && changedProps[i]) {
|
||||
@@ -859,9 +890,9 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
loadedProps[propertyIndex] = true;
|
||||
}
|
||||
|
||||
private void preGetterCallback() {
|
||||
private void preGetterCallback(int propertyIndex) {
|
||||
if (preGetterCallback != null) {
|
||||
preGetterCallback.preGetterTrigger();
|
||||
preGetterCallback.preGetterTrigger(propertyIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -869,14 +900,14 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
* Called prior to Id property getter.
|
||||
*/
|
||||
public void preGetId() {
|
||||
preGetterCallback();
|
||||
preGetterCallback(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that is called prior to a getter method on the actual entity.
|
||||
*/
|
||||
public void preGetter(int propertyIndex) {
|
||||
preGetterCallback();
|
||||
preGetterCallback(propertyIndex);
|
||||
if (state == STATE_NEW || disableLazyLoad) {
|
||||
return;
|
||||
}
|
||||
@@ -888,49 +919,17 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called for "enhancement" postSetter processing. This is around a PUTFIELD
|
||||
* so no need to check the newValue afterwards.
|
||||
*/
|
||||
public void postSetter(PropertyChangeEvent event) {
|
||||
if (pcs != null && event != null) {
|
||||
pcs.firePropertyChange(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called for "subclassed" postSetter processing. Here the newValue has to be
|
||||
* re-fetched (and passed into this method) in case there is code inside the
|
||||
* setter that further mutates the value.
|
||||
*/
|
||||
public void postSetter(PropertyChangeEvent event, Object newValue) {
|
||||
if (pcs != null && event != null) {
|
||||
if (newValue != null && newValue.equals(event.getNewValue())) {
|
||||
pcs.firePropertyChange(event);
|
||||
} else {
|
||||
pcs.firePropertyChange(event.getPropertyName(), event.getOldValue(), newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OneToMany and ManyToMany don't have any interception so just check for
|
||||
* PropertyChangeSupport.
|
||||
*/
|
||||
public PropertyChangeEvent preSetterMany(boolean interceptField, int propertyIndex, Object oldValue, Object newValue) {
|
||||
public void preSetterMany(boolean interceptField, int propertyIndex, Object oldValue, Object newValue) {
|
||||
|
||||
if (readOnly) {
|
||||
throw new IllegalStateException("This bean is readOnly");
|
||||
}
|
||||
|
||||
setLoadedProperty(propertyIndex);
|
||||
|
||||
// Bean itself not considered dirty when many changed
|
||||
if (pcs != null) {
|
||||
return new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void setChangedPropertyValue(int propertyIndex, boolean setDirtyState, Object origValue) {
|
||||
@@ -942,15 +941,19 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
|
||||
if (setDirtyState) {
|
||||
setOriginalValue(propertyIndex, origValue);
|
||||
if (!dirty) {
|
||||
dirty = true;
|
||||
if (embeddedOwner != null) {
|
||||
// Cascade dirty state from Embedded bean to parent bean
|
||||
embeddedOwner._ebean_getIntercept().setEmbeddedDirty(embeddedOwnerIndex);
|
||||
}
|
||||
if (nodeUsageCollector != null) {
|
||||
nodeUsageCollector.setModified();
|
||||
}
|
||||
setDirtyStatus();
|
||||
}
|
||||
}
|
||||
|
||||
private void setDirtyStatus() {
|
||||
if (!dirty) {
|
||||
dirty = true;
|
||||
if (embeddedOwner != null) {
|
||||
// Cascade dirty state from Embedded bean to parent bean
|
||||
embeddedOwner._ebean_getIntercept().setEmbeddedDirty(embeddedOwnerIndex);
|
||||
}
|
||||
if (nodeUsageCollector != null) {
|
||||
nodeUsageCollector.setModified();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -959,210 +962,156 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
* Check to see if the values are not equal. If they are not equal then create
|
||||
* the old values for use with ConcurrencyMode.ALL.
|
||||
*/
|
||||
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, Object oldValue, Object newValue) {
|
||||
public void preSetter(boolean intercept, int propertyIndex, Object oldValue, Object newValue) {
|
||||
|
||||
if (state == STATE_NEW) {
|
||||
setLoadedProperty(propertyIndex);
|
||||
} else if (!areEqual(oldValue, newValue)) {
|
||||
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check for primitive boolean.
|
||||
*/
|
||||
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, boolean oldValue, boolean newValue) {
|
||||
public void preSetter(boolean intercept, int propertyIndex, boolean oldValue, boolean newValue) {
|
||||
|
||||
if (state == STATE_NEW) {
|
||||
setLoadedProperty(propertyIndex);
|
||||
} else if (oldValue != newValue) {
|
||||
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for primitive int.
|
||||
*/
|
||||
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, int oldValue, int newValue) {
|
||||
public void preSetter(boolean intercept, int propertyIndex, int oldValue, int newValue) {
|
||||
|
||||
if (state == STATE_NEW) {
|
||||
setLoadedProperty(propertyIndex);
|
||||
} else if (oldValue != newValue) {
|
||||
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* long.
|
||||
*/
|
||||
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, long oldValue, long newValue) {
|
||||
public void preSetter(boolean intercept, int propertyIndex, long oldValue, long newValue) {
|
||||
|
||||
if (state == STATE_NEW) {
|
||||
setLoadedProperty(propertyIndex);
|
||||
} else if (oldValue != newValue) {
|
||||
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* double.
|
||||
*/
|
||||
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, double oldValue, double newValue) {
|
||||
public void preSetter(boolean intercept, int propertyIndex, double oldValue, double newValue) {
|
||||
|
||||
if (state == STATE_NEW) {
|
||||
setLoadedProperty(propertyIndex);
|
||||
} else if (oldValue != newValue) {
|
||||
} else if (Double.compare(oldValue, newValue) != 0) {
|
||||
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* float.
|
||||
*/
|
||||
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, float oldValue, float newValue) {
|
||||
public void preSetter(boolean intercept, int propertyIndex, float oldValue, float newValue) {
|
||||
|
||||
if (state == STATE_NEW) {
|
||||
setLoadedProperty(propertyIndex);
|
||||
} else if (oldValue != newValue) {
|
||||
} else if (Float.compare(oldValue, newValue) != 0) {
|
||||
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* short.
|
||||
*/
|
||||
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, short oldValue, short newValue) {
|
||||
public void preSetter(boolean intercept, int propertyIndex, short oldValue, short newValue) {
|
||||
|
||||
if (state == STATE_NEW) {
|
||||
setLoadedProperty(propertyIndex);
|
||||
} else if (oldValue != newValue) {
|
||||
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* char.
|
||||
*/
|
||||
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, char oldValue, char newValue) {
|
||||
public void preSetter(boolean intercept, int propertyIndex, char oldValue, char newValue) {
|
||||
|
||||
if (state == STATE_NEW) {
|
||||
setLoadedProperty(propertyIndex);
|
||||
} else if (oldValue != newValue) {
|
||||
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* byte.
|
||||
*/
|
||||
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, byte oldValue, byte newValue) {
|
||||
public void preSetter(boolean intercept, int propertyIndex, byte oldValue, byte newValue) {
|
||||
|
||||
if (state == STATE_NEW) {
|
||||
setLoadedProperty(propertyIndex);
|
||||
} else if (oldValue != newValue) {
|
||||
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* char[].
|
||||
*/
|
||||
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, char[] oldValue, char[] newValue) {
|
||||
public void preSetter(boolean intercept, int propertyIndex, char[] oldValue, char[] newValue) {
|
||||
|
||||
if (state == STATE_NEW) {
|
||||
setLoadedProperty(propertyIndex);
|
||||
} else if (!areEqualChars(oldValue, newValue)) {
|
||||
} else if (!Arrays.equals(oldValue, newValue)) {
|
||||
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* byte[].
|
||||
*/
|
||||
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, byte[] oldValue, byte[] newValue) {
|
||||
public void preSetter(boolean intercept, int propertyIndex, byte[] oldValue, byte[] newValue) {
|
||||
|
||||
if (state == STATE_NEW) {
|
||||
setLoadedProperty(propertyIndex);
|
||||
} else if (!areEqualBytes(oldValue, newValue)) {
|
||||
} else if (!Arrays.equals(oldValue, newValue)) {
|
||||
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
private static boolean areEqualBytes(byte[] b1, byte[] b2) {
|
||||
if (b1 == null) {
|
||||
return (b2 == null);
|
||||
|
||||
} else if (b2 == null) {
|
||||
return false;
|
||||
|
||||
} else if (b1 == b2) {
|
||||
return true;
|
||||
|
||||
} else if (b1.length != b2.length) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < b1.length; i++) {
|
||||
if (b1[i] != b2[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
/**
|
||||
* Explicitly set an old value with force (the old value is forced even it is already set).
|
||||
*/
|
||||
public void setOldValue(int propertyIndex, Object oldValue) {
|
||||
setChangedProperty(propertyIndex);
|
||||
setOriginalValueForce(propertyIndex, oldValue);
|
||||
setDirtyStatus();
|
||||
}
|
||||
|
||||
private static boolean areEqualChars(char[] b1, char[] b2) {
|
||||
if (b1 == null) {
|
||||
return (b2 == null);
|
||||
/**
|
||||
* Return the sort order value for an order column.
|
||||
*/
|
||||
public int getSortOrder() {
|
||||
return sortOrder;
|
||||
}
|
||||
|
||||
} else if (b2 == null) {
|
||||
return false;
|
||||
|
||||
} else if (b1 == b2) {
|
||||
return true;
|
||||
|
||||
} else if (b1.length != b2.length) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < b1.length; i++) {
|
||||
if (b1[i] != b2[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
/**
|
||||
* Set the sort order value for an order column.
|
||||
*/
|
||||
public void setSortOrder(int sortOrder) {
|
||||
this.sortOrder = sortOrder;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ package io.ebean.bean;
|
||||
public interface PreGetterCallback {
|
||||
|
||||
/**
|
||||
* Trigger the callback.
|
||||
* Trigger the callback based on a getter on a property.
|
||||
*
|
||||
* @param propertyIndex The index of the property
|
||||
*/
|
||||
void preGetterTrigger();
|
||||
void preGetterTrigger(int propertyIndex);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package io.ebean.cache;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* For query cache entries we additionally hold the dependent tables and timestamp for the query result.
|
||||
* <p>
|
||||
* We use the dependent tables and timestamp to validate that tables the query joins to have not been
|
||||
* modified since the query cache entry was cached. If any dependent tables have since been modified
|
||||
* the query cache entry is treated as invalid.
|
||||
* </p>
|
||||
*/
|
||||
public class QueryCacheEntry {
|
||||
|
||||
private final Object value;
|
||||
|
||||
private final Set<String> dependentTables;
|
||||
|
||||
private final long timestamp;
|
||||
|
||||
/**
|
||||
* Create with dependent tables and timestamp.
|
||||
*
|
||||
* @param value The query result being cached
|
||||
* @param dependentTables The extra tables the query is dependent on (joins to)
|
||||
* @param timestamp The timestamp that the query uses to check for modifications
|
||||
*/
|
||||
public QueryCacheEntry(Object value, Set<String> dependentTables, long timestamp) {
|
||||
this.value = value;
|
||||
this.dependentTables = dependentTables;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the actual query result.
|
||||
*/
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the tables the query result is dependent on.
|
||||
*/
|
||||
public Set<String> getDependentTables() {
|
||||
return dependentTables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the timestamp used to check for modifications on the dependent tables.
|
||||
*/
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.ebean.cache;
|
||||
|
||||
/**
|
||||
* Used to validate that a query cache entry is still valid based on dependent tables.
|
||||
*/
|
||||
public interface QueryCacheEntryValidate {
|
||||
|
||||
/**
|
||||
* Return true if the entry is still valid based on dependent tables.
|
||||
*/
|
||||
boolean isValid(QueryCacheEntry queryCacheEntry);
|
||||
}
|
||||
+32
-2
@@ -1,5 +1,9 @@
|
||||
package io.ebean.cache;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents part of the "L2" server side cache.
|
||||
* <p>
|
||||
@@ -13,20 +17,46 @@ package io.ebean.cache;
|
||||
*/
|
||||
public interface ServerCache {
|
||||
|
||||
default Map<Object, Object> getAll(Set<Object> keys) {
|
||||
|
||||
Map<Object, Object> map = new LinkedHashMap<>();
|
||||
for (Object key : keys) {
|
||||
Object value = get(key);
|
||||
if (value != null) {
|
||||
map.put(key, value);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value given the key.
|
||||
*/
|
||||
Object get(Object id);
|
||||
|
||||
/**
|
||||
* Put all the values in the cache.
|
||||
*/
|
||||
default void putAll(Map<Object, Object> keyValues) {
|
||||
keyValues.forEach(this::put);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put the value in the cache with a given id.
|
||||
*/
|
||||
Object put(Object id, Object value);
|
||||
void put(Object id, Object value);
|
||||
|
||||
/**
|
||||
* Remove the entries from the cache given the id values.
|
||||
*/
|
||||
default void removeAll(Set<Object> keys) {
|
||||
keys.forEach(this::remove);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a entry from the cache given its id.
|
||||
*/
|
||||
Object remove(Object id);
|
||||
void remove(Object id);
|
||||
|
||||
/**
|
||||
* Clear all entries from the cache.
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package io.ebean.cache;
|
||||
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
|
||||
/**
|
||||
* Configuration used to create ServerCache instances.
|
||||
*/
|
||||
public class ServerCacheConfig {
|
||||
|
||||
private final ServerCacheType type;
|
||||
private final String cacheKey;
|
||||
private final ServerCacheOptions cacheOptions;
|
||||
private final CurrentTenantProvider tenantProvider;
|
||||
private final QueryCacheEntryValidate queryCacheEntryValidate;
|
||||
|
||||
public ServerCacheConfig(ServerCacheType type, String cacheKey, ServerCacheOptions cacheOptions, CurrentTenantProvider tenantProvider, QueryCacheEntryValidate queryCacheEntryValidate) {
|
||||
this.type = type;
|
||||
this.cacheKey = cacheKey;
|
||||
this.cacheOptions = cacheOptions;
|
||||
this.tenantProvider = tenantProvider;
|
||||
this.queryCacheEntryValidate = queryCacheEntryValidate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the cache type.
|
||||
*/
|
||||
public ServerCacheType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the cache.
|
||||
*/
|
||||
public String getCacheKey() {
|
||||
return cacheKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the tuning options.
|
||||
*/
|
||||
public ServerCacheOptions getCacheOptions() {
|
||||
return cacheOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current tenant provider.
|
||||
*/
|
||||
public CurrentTenantProvider getTenantProvider() {
|
||||
return tenantProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the service that provides validation for query cache entries.
|
||||
*/
|
||||
public QueryCacheEntryValidate getQueryCacheEntryValidate() {
|
||||
return queryCacheEntryValidate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the cache is a query cache.
|
||||
*/
|
||||
public boolean isQueryCache() {
|
||||
return type == ServerCacheType.QUERY;
|
||||
}
|
||||
}
|
||||
+10
-1
@@ -8,6 +8,15 @@ public interface ServerCacheFactory {
|
||||
/**
|
||||
* Create the cache for the given type with options.
|
||||
*/
|
||||
ServerCache createCache(ServerCacheType type, String cacheKey, ServerCacheOptions cacheOptions);
|
||||
ServerCache createCache(ServerCacheConfig config);
|
||||
|
||||
/**
|
||||
* Return a ServerCacheNotify that we will send ServerCacheNotification events to.
|
||||
* <p>
|
||||
* This is used if a ServerCacheNotifyPlugin is not supplied.
|
||||
* </p>
|
||||
*
|
||||
* @param listener The listener that should be used to process the notification events.
|
||||
*/
|
||||
ServerCacheNotify createCacheNotify(ServerCacheNotify listener);
|
||||
}
|
||||
|
||||
@@ -44,4 +44,19 @@ public interface ServerCacheManager {
|
||||
*/
|
||||
void clearAll();
|
||||
|
||||
/**
|
||||
* Clear all the local caches.
|
||||
*
|
||||
* This is used when the L2 Cache is based on clustered near-caches (Like Ebean-K8s-L2Cache).
|
||||
* It is not used when the L2 cache is a distributed cache such as HazelCast or Ignite etc.
|
||||
*/
|
||||
void clearAllLocal();
|
||||
|
||||
/**
|
||||
* Clear the local caches for this bean type.
|
||||
*
|
||||
* This is used when the L2 Cache is based on clustered near-caches (Like Ebean-K8s-L2Cache).
|
||||
* It is not used when the L2 cache is a distributed cache such as HazelCast or Ignite etc.
|
||||
*/
|
||||
void clearLocal(Class<?> beanType);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package io.ebean.cache;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Notification event that dependent tables have been modified.
|
||||
* <p>
|
||||
* This is sent to other interested servers (in the cluster).
|
||||
* </p>
|
||||
*/
|
||||
public class ServerCacheNotification {
|
||||
|
||||
private final long modifyTimestamp;
|
||||
|
||||
private final Set<String> dependentTables;
|
||||
|
||||
public ServerCacheNotification(long modifyTimestamp, Set<String> dependentTables) {
|
||||
this.modifyTimestamp = modifyTimestamp;
|
||||
this.dependentTables = dependentTables;
|
||||
}
|
||||
|
||||
public long getModifyTimestamp() {
|
||||
return modifyTimestamp;
|
||||
}
|
||||
|
||||
public Set<String> getDependentTables() {
|
||||
return dependentTables;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.ebean.cache;
|
||||
|
||||
/**
|
||||
* Interface for both listening to notification changes and sending them to other members of the cluster.
|
||||
*/
|
||||
public interface ServerCacheNotify {
|
||||
|
||||
/**
|
||||
* Notify other server cache members of the table modifications or process the notifications.
|
||||
*/
|
||||
void notify(ServerCacheNotification notification);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package io.ebean.cache;
|
||||
|
||||
import io.ebean.config.ServerConfig;
|
||||
|
||||
/**
|
||||
* Plugin that provides a ServerCacheNotify implementation.
|
||||
* <p>
|
||||
* Is supplied this will be used to send the ServerCacheNotification event to other cluster members.
|
||||
* </p>
|
||||
*/
|
||||
public interface ServerCacheNotifyPlugin {
|
||||
|
||||
/**
|
||||
* Create a ServerCacheNotify implementation given the server configuration.
|
||||
*/
|
||||
ServerCacheNotify create(ServerConfig serverConfig);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package io.ebean.cache;
|
||||
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Tenant aware handling for caching.
|
||||
*/
|
||||
public class TenantAwareKey {
|
||||
|
||||
private final CurrentTenantProvider tenantProvider;
|
||||
|
||||
/**
|
||||
* Construct with a tenant provider than can be null.
|
||||
*/
|
||||
public TenantAwareKey(CurrentTenantProvider tenantProvider) {
|
||||
this.tenantProvider = tenantProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a tenant aware key.
|
||||
*/
|
||||
public Object key(Object key) {
|
||||
if (tenantProvider != null) {
|
||||
return new CacheKey(key, tenantProvider.currentId());
|
||||
} else {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We use a combined key, if this serverCache is per tenant.
|
||||
*/
|
||||
public static final class CacheKey implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
final Object key;
|
||||
final Object tenantId;
|
||||
|
||||
/**
|
||||
* Create with optional tenantId.
|
||||
*/
|
||||
public CacheKey(Object key, Object tenantId) {
|
||||
this.key = key;
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = key.hashCode();
|
||||
result = 92821 * result + Objects.hashCode(tenantId);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof CacheKey) {
|
||||
CacheKey that = (CacheKey) obj;
|
||||
return Objects.equals(that.key, this.key)
|
||||
&& Objects.equals(that.tenantId, this.tenantId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder(key.toString());
|
||||
if (tenantId != null) {
|
||||
sb.append(":").append(tenantId);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.BeanCollectionLoader;
|
||||
import io.ebean.bean.EntityBean;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -47,8 +46,7 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
|
||||
protected ModifyHolder<E> modifyHolder;
|
||||
|
||||
protected ModifyListenMode modifyListenMode;
|
||||
protected boolean modifyAddListening;
|
||||
protected boolean modifyRemoveListening;
|
||||
|
||||
protected boolean modifyListening;
|
||||
|
||||
/**
|
||||
@@ -65,7 +63,7 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
|
||||
this.ebeanServerName = loader.getName();
|
||||
this.ownerBean = ownerBean;
|
||||
this.propertyName = propertyName;
|
||||
this.readOnly = ownerBean._ebean_getIntercept().isReadOnly();
|
||||
this.readOnly = ownerBean != null && ownerBean._ebean_getIntercept().isReadOnly();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -97,13 +95,6 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
|
||||
if (loader == null) {
|
||||
loader = (BeanCollectionLoader) Ebean.getServer(ebeanServerName);
|
||||
}
|
||||
if (loader == null) {
|
||||
String msg = "Lazy loading but LazyLoadEbeanServer is null?"
|
||||
+ " The LazyLoadEbeanServer needs to be set after deserialization"
|
||||
+ " to support lazy loading.";
|
||||
throw new PersistenceException(msg);
|
||||
}
|
||||
|
||||
loader.loadMany(this, onlyIds);
|
||||
checkEmptyLazyLoad();
|
||||
}
|
||||
@@ -141,6 +132,11 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
|
||||
// Support for modify additions deletions etc - ManyToMany
|
||||
// ---------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public ModifyListenMode getModifyListening() {
|
||||
return modifyListenMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* set modifyListening to be on or off.
|
||||
*/
|
||||
@@ -148,9 +144,7 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
|
||||
public void setModifyListening(ModifyListenMode mode) {
|
||||
|
||||
this.modifyListenMode = mode;
|
||||
this.modifyAddListening = ModifyListenMode.ALL.equals(mode);
|
||||
this.modifyRemoveListening = modifyAddListening || ModifyListenMode.REMOVALS.equals(mode);
|
||||
this.modifyListening = modifyRemoveListening || modifyAddListening;
|
||||
this.modifyListening = mode != null && ModifyListenMode.NONE != mode;
|
||||
if (modifyListening) {
|
||||
// lose any existing modifications
|
||||
modifyHolder = null;
|
||||
@@ -173,14 +167,14 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
|
||||
|
||||
@Override
|
||||
public void modifyAddition(E bean) {
|
||||
if (modifyAddListening) {
|
||||
if (modifyListening) {
|
||||
getModifyHolder().modifyAddition(bean);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modifyRemoval(Object bean) {
|
||||
if (modifyRemoveListening) {
|
||||
if (modifyListening) {
|
||||
getModifyHolder().modifyRemoval(bean);
|
||||
}
|
||||
}
|
||||
@@ -213,7 +207,25 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
|
||||
/**
|
||||
* Return true if there are underlying additions or removals.
|
||||
*/
|
||||
boolean holdsModifications() {
|
||||
@Override
|
||||
public boolean holdsModifications() {
|
||||
return modifyHolder != null && modifyHolder.hasModifications();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean wasTouched() {
|
||||
return modifyHolder != null && modifyHolder.wasTouched();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies all relevant properties for a clone. See {@link #getShallowCopy()}
|
||||
* @param other
|
||||
*/
|
||||
protected void setFromOriginal(AbstractBeanCollection<E> other) {
|
||||
this.disableLazyLoad = other.disableLazyLoad;
|
||||
this.ebeanServerName = other.ebeanServerName;
|
||||
this.loader = other.loader;
|
||||
this.ownerBean = other.ownerBean;
|
||||
this.propertyName = other.propertyName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,7 +232,7 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
|
||||
public void add(int index, E element) {
|
||||
checkReadOnly();
|
||||
init();
|
||||
if (modifyAddListening) {
|
||||
if (modifyListening) {
|
||||
modifyAddition(element);
|
||||
}
|
||||
list.add(index, element);
|
||||
@@ -247,7 +247,7 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
|
||||
public boolean add(E o) {
|
||||
checkReadOnly();
|
||||
init();
|
||||
if (modifyAddListening) {
|
||||
if (modifyListening) {
|
||||
if (list.add(o)) {
|
||||
modifyAddition(o);
|
||||
return true;
|
||||
@@ -262,7 +262,7 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
|
||||
public boolean addAll(Collection<? extends E> c) {
|
||||
checkReadOnly();
|
||||
init();
|
||||
if (modifyAddListening) {
|
||||
if (modifyListening) {
|
||||
// all elements in c are added (no contains checking)
|
||||
getModifyHolder().modifyAdditionAll(c);
|
||||
}
|
||||
@@ -273,7 +273,7 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
|
||||
public boolean addAll(int index, Collection<? extends E> c) {
|
||||
checkReadOnly();
|
||||
init();
|
||||
if (modifyAddListening) {
|
||||
if (modifyListening) {
|
||||
// all elements in c are added (no contains checking)
|
||||
getModifyHolder().modifyAdditionAll(c);
|
||||
}
|
||||
@@ -286,7 +286,7 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
|
||||
// TODO: when clear() and not initialised could be more clever
|
||||
// and fetch just the Id's
|
||||
initClear();
|
||||
if (modifyRemoveListening) {
|
||||
if (modifyListening) {
|
||||
for (E aList : list) {
|
||||
getModifyHolder().modifyRemoval(aList);
|
||||
}
|
||||
@@ -380,7 +380,7 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
|
||||
public E remove(int index) {
|
||||
checkReadOnly();
|
||||
init();
|
||||
if (modifyRemoveListening) {
|
||||
if (modifyListening) {
|
||||
E o = list.remove(index);
|
||||
modifyRemoval(o);
|
||||
return o;
|
||||
@@ -392,7 +392,7 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
|
||||
public boolean remove(Object o) {
|
||||
checkReadOnly();
|
||||
init();
|
||||
if (modifyRemoveListening) {
|
||||
if (modifyListening) {
|
||||
boolean isRemove = list.remove(o);
|
||||
if (isRemove) {
|
||||
modifyRemoval(o);
|
||||
@@ -406,7 +406,7 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
|
||||
public boolean removeAll(Collection<?> beans) {
|
||||
checkReadOnly();
|
||||
init();
|
||||
if (modifyRemoveListening) {
|
||||
if (modifyListening) {
|
||||
boolean changed = false;
|
||||
for (Object bean : beans) {
|
||||
if (list.remove(bean)) {
|
||||
@@ -424,7 +424,7 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
|
||||
public boolean retainAll(Collection<?> retainBeans) {
|
||||
checkReadOnly();
|
||||
init();
|
||||
if (modifyRemoveListening) {
|
||||
if (modifyListening) {
|
||||
boolean changed = false;
|
||||
Iterator<E> it = list.iterator();
|
||||
while (it.hasNext()) {
|
||||
@@ -541,4 +541,11 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanCollection<E> getShallowCopy() {
|
||||
BeanList<E> copy = new BeanList<>(new CopyOnFirstWriteList<>(list));
|
||||
copy.setFromOriginal(this);
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
public void clear() {
|
||||
checkReadOnly();
|
||||
initClear();
|
||||
if (modifyRemoveListening) {
|
||||
if (modifyListening) {
|
||||
// add all beans to the removal list
|
||||
for (E bean : map.values()) {
|
||||
modifyRemoval(bean);
|
||||
@@ -313,7 +313,7 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
public E remove(Object key) {
|
||||
checkReadOnly();
|
||||
init();
|
||||
if (modifyRemoveListening) {
|
||||
if (modifyListening) {
|
||||
E o = map.remove(key);
|
||||
modifyRemoval(o);
|
||||
return o;
|
||||
@@ -340,4 +340,10 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
return map.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanCollection<E> getShallowCopy() {
|
||||
BeanMap<K, E> copy = new BeanMap<>(new LinkedHashMap<>(map));
|
||||
copy.setFromOriginal(this);
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
|
||||
public boolean add(E o) {
|
||||
checkReadOnly();
|
||||
init();
|
||||
if (modifyAddListening) {
|
||||
if (modifyListening) {
|
||||
if (set.add(o)) {
|
||||
modifyAddition(o);
|
||||
return true;
|
||||
@@ -225,7 +225,7 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
|
||||
public boolean addAll(Collection<? extends E> addCollection) {
|
||||
checkReadOnly();
|
||||
init();
|
||||
if (modifyAddListening) {
|
||||
if (modifyListening) {
|
||||
boolean changed = false;
|
||||
for (E bean : addCollection) {
|
||||
if (set.add(bean)) {
|
||||
@@ -243,7 +243,7 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
|
||||
public void clear() {
|
||||
checkReadOnly();
|
||||
initClear();
|
||||
if (modifyRemoveListening) {
|
||||
if (modifyListening) {
|
||||
for (E bean : set) {
|
||||
modifyRemoval(bean);
|
||||
}
|
||||
@@ -285,7 +285,7 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
|
||||
public boolean remove(Object o) {
|
||||
checkReadOnly();
|
||||
init();
|
||||
if (modifyRemoveListening) {
|
||||
if (modifyListening) {
|
||||
if (set.remove(o)) {
|
||||
modifyRemoval(o);
|
||||
return true;
|
||||
@@ -299,7 +299,7 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
|
||||
public boolean removeAll(Collection<?> beans) {
|
||||
checkReadOnly();
|
||||
init();
|
||||
if (modifyRemoveListening) {
|
||||
if (modifyListening) {
|
||||
boolean changed = false;
|
||||
for (Object bean : beans) {
|
||||
if (set.remove(bean)) {
|
||||
@@ -316,7 +316,7 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
|
||||
public boolean retainAll(Collection<?> beans) {
|
||||
checkReadOnly();
|
||||
init();
|
||||
if (modifyRemoveListening) {
|
||||
if (modifyListening) {
|
||||
boolean changed = false;
|
||||
Iterator<?> it = set.iterator();
|
||||
while (it.hasNext()) {
|
||||
@@ -377,5 +377,11 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
|
||||
throw new IllegalStateException("This collection is in ReadOnly mode");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public BeanCollection<E> getShallowCopy() {
|
||||
BeanSet<E> copy = new BeanSet<>(new LinkedHashSet<>(set));
|
||||
copy.setFromOriginal(this);
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
package io.ebean.common;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.AbstractList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
/**
|
||||
* List that copies itself on first write access. Needed to keep memory footprint low and the ability
|
||||
* to modify lists from cache.
|
||||
*
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
*/
|
||||
public final class CopyOnFirstWriteList<E> extends AbstractList<E> implements List<E>, Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* The underlying List implementation.
|
||||
*/
|
||||
private List<E> list;
|
||||
|
||||
|
||||
public CopyOnFirstWriteList(List<E> list) {
|
||||
super();
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
private volatile boolean copied = false;
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return list.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return list.contains(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return list.toArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T[] toArray(T[] a) {
|
||||
return list.toArray(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(E e) {
|
||||
checkCopyOnWrite();
|
||||
return list.add(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
checkCopyOnWrite();
|
||||
return list.remove(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> c) {
|
||||
return list.containsAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends E> c) {
|
||||
checkCopyOnWrite();
|
||||
return list.addAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(int index, Collection<? extends E> c) {
|
||||
checkCopyOnWrite();
|
||||
return list.addAll(index, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
checkCopyOnWrite();
|
||||
return list.removeAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
checkCopyOnWrite();
|
||||
return list.retainAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceAll(UnaryOperator<E> operator) {
|
||||
checkCopyOnWrite();
|
||||
list.replaceAll(operator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeIf(Predicate<? super E> filter) {
|
||||
checkCopyOnWrite();
|
||||
return list.removeIf(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sort(Comparator<? super E> c) {
|
||||
checkCopyOnWrite();
|
||||
list.sort(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
if (!copied) {
|
||||
list = new ArrayList<>();
|
||||
copied = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return list.equals(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return list.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public E get(int index) {
|
||||
return list.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public E set(int index, E element) {
|
||||
checkCopyOnWrite();
|
||||
return list.set(index, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int index, E element) {
|
||||
checkCopyOnWrite();
|
||||
list.add(index, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public E remove(int index) {
|
||||
checkCopyOnWrite();
|
||||
return list.remove(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(Object o) {
|
||||
return list.indexOf(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastIndexOf(Object o) {
|
||||
return list.lastIndexOf(o);
|
||||
}
|
||||
|
||||
private void checkCopyOnWrite() {
|
||||
if (!copied) {
|
||||
synchronized (this) {
|
||||
if (!copied) {
|
||||
list = new ArrayList<>(list);
|
||||
copied = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,10 @@ class ModifyHolder<E> implements Serializable {
|
||||
*/
|
||||
private Set<E> modifyAdditions = new LinkedHashSet<>();
|
||||
|
||||
private boolean touched;
|
||||
|
||||
void reset() {
|
||||
touched = false;
|
||||
modifyDeletions = new LinkedHashSet<>();
|
||||
modifyAdditions = new LinkedHashSet<>();
|
||||
}
|
||||
@@ -44,21 +47,31 @@ class ModifyHolder<E> implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean undoDeletion(E bean) {
|
||||
return (bean != null) && modifyDeletions.remove(bean);
|
||||
}
|
||||
|
||||
void modifyAddition(E bean) {
|
||||
if (bean != null) {
|
||||
touched = true;
|
||||
// If it is to delete then just remove the deletion
|
||||
if (!modifyDeletions.remove(bean)) {
|
||||
if (!undoDeletion(bean)) {
|
||||
// Insert
|
||||
modifyAdditions.add(bean);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean undoAddition(Object bean) {
|
||||
return (bean != null) && modifyAdditions.remove(bean);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
void modifyRemoval(Object bean) {
|
||||
if (bean != null) {
|
||||
touched = true;
|
||||
// If it is to be added then just remove the addition
|
||||
if (!modifyAdditions.remove(bean)) {
|
||||
if (!undoAddition(bean)) {
|
||||
modifyDeletions.add((E) bean);
|
||||
}
|
||||
}
|
||||
@@ -72,6 +85,14 @@ class ModifyHolder<E> implements Serializable {
|
||||
return modifyDeletions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the collection was touched in some way. This is still true even if
|
||||
* a bean was removed and then added (which is not held as a modification).
|
||||
*/
|
||||
boolean wasTouched() {
|
||||
return touched;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there additions or removals.
|
||||
*/
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package io.ebean.config;
|
||||
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.util.AnnotationUtil;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.persistence.DiscriminatorValue;
|
||||
import javax.persistence.Inheritance;
|
||||
import javax.persistence.Table;
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
/**
|
||||
* Provides some base implementation for NamingConventions.
|
||||
@@ -14,16 +16,15 @@ import java.lang.annotation.Annotation;
|
||||
*/
|
||||
public abstract class AbstractNamingConvention implements NamingConvention {
|
||||
|
||||
private static final Pattern TABLE_REPLACE = Pattern.compile("{table}", Pattern.LITERAL);
|
||||
|
||||
private static final Pattern COLUMN_REPLACE = Pattern.compile("{column}", Pattern.LITERAL);
|
||||
|
||||
/**
|
||||
* The Constant DEFAULT_SEQ_FORMAT.
|
||||
*/
|
||||
public static final String DEFAULT_SEQ_FORMAT = "{table}_seq";
|
||||
|
||||
/**
|
||||
* Sequence Format that includes the Primary Key column
|
||||
*/
|
||||
public static final String TABLE_PKCOLUMN_SEQ_FORMAT = "{table}_{column}_seq";
|
||||
|
||||
/**
|
||||
* The catalog.
|
||||
*/
|
||||
@@ -83,11 +84,11 @@ public abstract class AbstractNamingConvention implements NamingConvention {
|
||||
|
||||
@Override
|
||||
public String getSequenceName(String tableName, String pkColumn) {
|
||||
String s = sequenceFormat.replace("{table}", tableName);
|
||||
String s = TABLE_REPLACE.matcher(sequenceFormat).replaceAll(Matcher.quoteReplacement(tableName));
|
||||
if (pkColumn == null) {
|
||||
pkColumn = "";
|
||||
}
|
||||
return s.replace("{column}", pkColumn);
|
||||
return COLUMN_REPLACE.matcher(s).replaceAll(Matcher.quoteReplacement(pkColumn));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,45 +186,43 @@ public abstract class AbstractNamingConvention implements NamingConvention {
|
||||
*/
|
||||
@Override
|
||||
public TableName getTableName(Class<?> beanClass) {
|
||||
while (true) {
|
||||
|
||||
TableName tableName = getTableNameFromAnnotation(beanClass);
|
||||
if (tableName == null) {
|
||||
Class<?> supCls = beanClass.getSuperclass();
|
||||
if (hasInheritance(supCls)) {
|
||||
// get the table as per inherited class in case there
|
||||
// is not a table annotation in the inheritance hierarchy
|
||||
return getTableName(supCls);
|
||||
TableName tableName = getTableNameFromAnnotation(beanClass);
|
||||
if (tableName == null) {
|
||||
Class<?> supCls = beanClass.getSuperclass();
|
||||
if (hasInheritance(supCls)) {
|
||||
// get the table as per inherited class in case there
|
||||
// is not a table annotation in the inheritance hierarchy
|
||||
beanClass = supCls;
|
||||
continue;
|
||||
}
|
||||
|
||||
tableName = getTableNameByConvention(beanClass);
|
||||
}
|
||||
|
||||
tableName = getTableNameByConvention(beanClass);
|
||||
// Use naming convention for catalog or schema,
|
||||
// if not set in the annotation.
|
||||
String catalog = tableName.getCatalog();
|
||||
if (isEmpty(catalog)) {
|
||||
catalog = getCatalog();
|
||||
}
|
||||
String schema = tableName.getSchema();
|
||||
if (isEmpty(schema)) {
|
||||
schema = getSchema();
|
||||
}
|
||||
return new TableName(catalog, schema, tableName.getName());
|
||||
}
|
||||
|
||||
// Use naming convention for catalog or schema,
|
||||
// if not set in the annotation.
|
||||
String catalog = tableName.getCatalog();
|
||||
if (isEmpty(catalog)) {
|
||||
catalog = getCatalog();
|
||||
}
|
||||
String schema = tableName.getSchema();
|
||||
if (isEmpty(schema)) {
|
||||
schema = getSchema();
|
||||
}
|
||||
return new TableName(catalog, schema, tableName.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this class is part of entity inheritance.
|
||||
*/
|
||||
protected boolean hasInheritance(Class<?> supCls) {
|
||||
return hasAnnotation(supCls, Inheritance.class) || hasAnnotation(supCls, DiscriminatorValue.class);
|
||||
return AnnotationUtil.findAnnotationRecursive(supCls, Inheritance.class) != null
|
||||
|| AnnotationUtil.findAnnotation(supCls, DiscriminatorValue.class) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the class has the given annotation.
|
||||
*/
|
||||
protected boolean hasAnnotation(Class<?> supCls, Class<? extends Annotation> annotation) {
|
||||
return supCls.getAnnotation(annotation) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableName getM2MJoinTableName(TableName lhsTable, TableName rhsTable) {
|
||||
@@ -254,7 +253,7 @@ public abstract class AbstractNamingConvention implements NamingConvention {
|
||||
*/
|
||||
protected TableName getTableNameFromAnnotation(Class<?> beanClass) {
|
||||
|
||||
final Table t = findTableAnnotation(beanClass);
|
||||
final Table t = AnnotationUtil.findAnnotationRecursive(beanClass, Table.class);
|
||||
|
||||
// Take the annotation if defined
|
||||
if (t != null && !isEmpty(t.name())) {
|
||||
@@ -267,20 +266,6 @@ public abstract class AbstractNamingConvention implements NamingConvention {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search recursively for an @Table in the class hierarchy.
|
||||
*/
|
||||
protected Table findTableAnnotation(Class<?> cls) {
|
||||
if (cls.equals(Object.class)) {
|
||||
return null;
|
||||
}
|
||||
Table table = cls.getAnnotation(Table.class);
|
||||
if (table != null) {
|
||||
return table;
|
||||
}
|
||||
return findTableAnnotation(cls.getSuperclass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace back ticks (if they are used) with database platform specific
|
||||
* quoted identifiers.
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package io.ebean.config;
|
||||
|
||||
/**
|
||||
* Used to provide some automatic configuration early in the creation of an EbeanServer.
|
||||
*/
|
||||
public interface AutoConfigure {
|
||||
|
||||
/**
|
||||
* Perform configuration for the ServerConfig prior to properties load.
|
||||
*/
|
||||
void preConfigure(ServerConfig serverConfig);
|
||||
|
||||
/**
|
||||
* Provide some configuration the ServerConfig prior to server creation but after properties have been applied.
|
||||
*/
|
||||
void postConfigure(ServerConfig serverConfig);
|
||||
|
||||
}
|
||||
@@ -32,6 +32,15 @@ public class AutoTuneConfig {
|
||||
public AutoTuneConfig() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if we are profiling or query tuning.
|
||||
*
|
||||
* If we are not doing either then we don't need a CallStack.
|
||||
*/
|
||||
public boolean isActive() {
|
||||
return profiling || queryTuning;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the file that holds the query tuning information.
|
||||
*/
|
||||
|
||||
@@ -54,6 +54,22 @@ public class ClassLoadConfig {
|
||||
return isPresent("javax.validation.constraints.NotNull");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if javax PostConstruct annotation is present (maybe not in java9).
|
||||
* If not we don't support PostConstruct lifecycle events.
|
||||
*/
|
||||
public boolean isJavaxPostConstructPresent() {
|
||||
return isPresent("javax.annotation.PostConstruct");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if javax JAXB is present (maybe not in java9).
|
||||
* If not we don't try to parse or support 'extra ddl'.
|
||||
*/
|
||||
public boolean isJavaxJAXBPresent() {
|
||||
return isPresent("javax.xml.bind.JAXBException");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if Jackson annotations like JsonIgnore are present.
|
||||
*/
|
||||
@@ -118,7 +134,7 @@ public class ClassLoadConfig {
|
||||
/**
|
||||
* Wraps the preferred, caller and context class loaders.
|
||||
*/
|
||||
protected class ClassLoaderContext {
|
||||
protected static class ClassLoaderContext {
|
||||
|
||||
/**
|
||||
* Optional - if set only use this classLoader (no fallback).
|
||||
|
||||
@@ -10,22 +10,82 @@ import java.util.Properties;
|
||||
*/
|
||||
public class ContainerConfig {
|
||||
|
||||
protected boolean clusterActive;
|
||||
private boolean active;
|
||||
private String serviceName;
|
||||
private String namespace;
|
||||
private String podName;
|
||||
private int port;
|
||||
|
||||
protected Properties properties;
|
||||
private Properties properties;
|
||||
|
||||
/**
|
||||
* Return the service name.
|
||||
*/
|
||||
public String getServiceName() {
|
||||
return serviceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the service name.
|
||||
*/
|
||||
public void setServiceName(String serviceName) {
|
||||
this.serviceName = serviceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the namespace.
|
||||
*/
|
||||
public String getNamespace() {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the namespace.
|
||||
*/
|
||||
public void setNamespace(String namespace) {
|
||||
this.namespace = namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the pod name.
|
||||
*/
|
||||
public String getPodName() {
|
||||
return podName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the pod name.
|
||||
*/
|
||||
public void setPodName(String podName) {
|
||||
this.podName = podName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the port to use.
|
||||
*/
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the port to use.
|
||||
*/
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if clustering is active.
|
||||
*/
|
||||
public boolean isClusterActive() {
|
||||
return clusterActive;
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true for clustering to be active.
|
||||
*/
|
||||
public void setClusterActive(boolean clusterActive) {
|
||||
this.clusterActive = clusterActive;
|
||||
public void setActive(boolean active) {
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,7 +107,14 @@ public class ContainerConfig {
|
||||
*/
|
||||
public void loadFromProperties(Properties properties) {
|
||||
this.properties = properties;
|
||||
this.clusterActive = getProperty(properties, "ebean.cluster.active", clusterActive);
|
||||
this.active = getProperty(properties, "ebean.cluster.active", active);
|
||||
this.serviceName = properties.getProperty("ebean.cluster.serviceName", serviceName);
|
||||
this.namespace = properties.getProperty("ebean.cluster.namespace", namespace);
|
||||
this.podName = properties.getProperty("ebean.cluster.podName", podName);
|
||||
String portParam = properties.getProperty("ebean.cluster.port");
|
||||
if (portParam != null) {
|
||||
this.port = Integer.parseInt(portParam);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.ebean.config;
|
||||
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
import io.ebean.Platform;
|
||||
import io.ebean.annotation.Platform;
|
||||
|
||||
/**
|
||||
* Custom mappings for DB types that override the default.
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
package io.ebean.config;
|
||||
|
||||
import io.ebean.Platform;
|
||||
import io.ebean.dbmigration.MigrationConfig;
|
||||
import io.ebean.dbmigration.MigrationRunner;
|
||||
import io.ebean.EbeanVersion;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.migration.MigrationConfig;
|
||||
import io.ebean.migration.MigrationRunner;
|
||||
import io.ebean.util.StringHelper;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Configuration for the DB migration processing.
|
||||
@@ -15,6 +21,8 @@ public class DbMigrationConfig {
|
||||
|
||||
protected static final Logger logger = LoggerFactory.getLogger(DbMigrationConfig.class);
|
||||
|
||||
protected MigrationConfig runnerConfig = new MigrationConfig();
|
||||
|
||||
/**
|
||||
* The database platform to generate migration DDL for.
|
||||
*/
|
||||
@@ -94,6 +102,11 @@ public class DbMigrationConfig {
|
||||
*/
|
||||
protected Map<String, String> runPlaceholderMap;
|
||||
|
||||
/**
|
||||
* DB schema used for the migration (and testing).
|
||||
*/
|
||||
protected String dbSchema;
|
||||
|
||||
/**
|
||||
* DB user used to run the DB migration.
|
||||
*/
|
||||
@@ -104,6 +117,20 @@ public class DbMigrationConfig {
|
||||
*/
|
||||
protected String dbPassword;
|
||||
|
||||
protected String patchInsertOn;
|
||||
|
||||
protected String patchResetChecksumOn;
|
||||
|
||||
/**
|
||||
* Mode used to check non-null columns added via migration have a default value specified etc.
|
||||
*/
|
||||
protected boolean strictMode = true;
|
||||
|
||||
/**
|
||||
* Contains the DDL-header information.
|
||||
*/
|
||||
protected String ddlHeader;
|
||||
|
||||
/**
|
||||
* Return the DB platform to generate migration DDL for.
|
||||
* <p>
|
||||
@@ -351,6 +378,111 @@ public class DbMigrationConfig {
|
||||
this.dbPassword = dbPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB schema to use (for migration, testing etc).
|
||||
*/
|
||||
public String getDbSchema() {
|
||||
String schema = readEnvironment("ddl.migration.schema");
|
||||
if (schema != null) {
|
||||
return schema;
|
||||
}
|
||||
return dbSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Db schema to use.
|
||||
*/
|
||||
public void setDbSchema(String dbSchema) {
|
||||
this.dbSchema = dbSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Db schema if it hasn't already been defined.
|
||||
*/
|
||||
public void setDefaultDbSchema(String dbSchema) {
|
||||
if (this.dbSchema == null) {
|
||||
this.dbSchema = dbSchema;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return migration versions that should be added to history without running.
|
||||
*/
|
||||
public String getPatchInsertOn() {
|
||||
return patchInsertOn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set migration versions that should be added to history without running.
|
||||
* <p>
|
||||
* Value can be a string containing comma delimited list of version numbers.
|
||||
* </p>
|
||||
*/
|
||||
public void setPatchInsertOn(String patchInsertOn) {
|
||||
this.patchInsertOn = patchInsertOn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return migration versions that should have their checksum reset and not run.
|
||||
*/
|
||||
public String getPatchResetChecksumOn() {
|
||||
return patchResetChecksumOn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a DDL header prepend for each DDL. E.g. for copyright headers
|
||||
* You can use placeholders like ${version} or ${timestamp} in properties file.
|
||||
*/
|
||||
public String getDdlHeader() {
|
||||
if (ddlHeader != null && !ddlHeader.isEmpty()) {
|
||||
ddlHeader = StringHelper.replaceString(ddlHeader, "${version}", EbeanVersion.getVersion());
|
||||
ddlHeader = StringHelper.replaceString(ddlHeader, "${timestamp}", ZonedDateTime.now().format( DateTimeFormatter.ISO_INSTANT ));
|
||||
}
|
||||
return ddlHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the header prepended to the DDL.
|
||||
*/
|
||||
public void setDdlHeader(String ddlHeader) {
|
||||
this.ddlHeader = ddlHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set migration versions that should have their checksum reset and not run.
|
||||
* <p>
|
||||
* Value can be a string containing comma delimited list of version numbers.
|
||||
* </p>
|
||||
*/
|
||||
public void setPatchResetChecksumOn(String patchResetChecksumOn) {
|
||||
this.patchResetChecksumOn = patchResetChecksumOn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if strict mode is used which includes a check that non-null columns have a default value.
|
||||
*/
|
||||
public boolean isStrictMode() {
|
||||
String envValue = readEnvironment("ddl.migration.strictMode");
|
||||
if (!isEmpty(envValue)) {
|
||||
return Boolean.parseBoolean(envValue.trim());
|
||||
}
|
||||
return strictMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to false to turn off strict mode allowing non-null columns to not have a default value.
|
||||
*/
|
||||
public void setStrictMode(boolean strictMode) {
|
||||
this.strictMode = strictMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying migration runner configuration allowing for more advanced settings.
|
||||
*/
|
||||
public MigrationConfig getRunnerConfig() {
|
||||
return runnerConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the settings from the PropertiesWrapper.
|
||||
*/
|
||||
@@ -369,18 +501,24 @@ public class DbMigrationConfig {
|
||||
generate = properties.getBoolean("migration.generate", generate);
|
||||
version = properties.get("migration.version", version);
|
||||
name = properties.get("migration.name", name);
|
||||
patchInsertOn = properties.get("migration.patchInsertOn", patchInsertOn);
|
||||
patchResetChecksumOn = properties.get("migration.patchResetChecksumOn", patchResetChecksumOn);
|
||||
|
||||
runMigration = properties.getBoolean("migration.run", runMigration);
|
||||
metaTable = properties.get("migration.metaTable", metaTable);
|
||||
runPlaceholders = properties.get("migration.placeholders", runPlaceholders);
|
||||
dbSchema = properties.get("migration.dbSchema", dbSchema);
|
||||
|
||||
String adminUser = properties.get("datasource." + serverName + ".username", dbUsername);
|
||||
adminUser = properties.get("datasource." + serverName + ".adminusername", adminUser);
|
||||
//Do not set user and pass from "datasource.db.username"
|
||||
//There is a null test in MigrationRunner::getConnection to handle this
|
||||
//String adminUser = properties.get("datasource." + serverName + ".username", dbUsername);
|
||||
String adminUser = properties.get("datasource." + serverName + ".adminusername", dbUsername);
|
||||
dbUsername = properties.get("migration.dbusername", adminUser);
|
||||
|
||||
String adminPwd = properties.get("datasource." + serverName + ".password", dbPassword);
|
||||
adminPwd = properties.get("datasource." + serverName + ".adminpassword", adminPwd);
|
||||
//String adminPwd = properties.get("datasource." + serverName + ".password", dbPassword);
|
||||
String adminPwd = properties.get("datasource." + serverName + ".adminpassword", dbPassword);
|
||||
dbPassword = properties.get("migration.dbpassword", adminPwd);
|
||||
ddlHeader = properties.get("ddl.header", ddlHeader);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -443,9 +581,8 @@ public class DbMigrationConfig {
|
||||
/**
|
||||
* Create the MigrationRunner to run migrations if necessary.
|
||||
*/
|
||||
public MigrationRunner createRunner(ClassLoader classLoader) {
|
||||
public MigrationRunner createRunner(ClassLoader classLoader, Properties properties) {
|
||||
|
||||
MigrationConfig runnerConfig = new MigrationConfig();
|
||||
runnerConfig.setMetaTable(metaTable);
|
||||
runnerConfig.setApplySuffix(applySuffix);
|
||||
runnerConfig.setMigrationPath(migrationPath);
|
||||
@@ -453,7 +590,17 @@ public class DbMigrationConfig {
|
||||
runnerConfig.setRunPlaceholders(runPlaceholders);
|
||||
runnerConfig.setDbUsername(getDbUsername());
|
||||
runnerConfig.setDbPassword(getDbPassword());
|
||||
runnerConfig.setDbSchema(getDbSchema());
|
||||
runnerConfig.setClassLoader(classLoader);
|
||||
if (patchInsertOn != null) {
|
||||
runnerConfig.setPatchInsertOn(patchInsertOn);
|
||||
}
|
||||
if (patchResetChecksumOn != null) {
|
||||
runnerConfig.setPatchResetChecksumOn(patchResetChecksumOn);
|
||||
}
|
||||
if (properties != null) {
|
||||
runnerConfig.load(properties);
|
||||
}
|
||||
return new MigrationRunner(runnerConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
package io.ebean.config;
|
||||
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
import io.ebean.config.dbplatform.IdType;
|
||||
import io.ebean.Platform;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Configuration for DB types such as UUID, Geometry etc.
|
||||
*/
|
||||
public class DbTypeConfig {
|
||||
|
||||
/**
|
||||
* The Geometry SRID value (default 4326).
|
||||
*/
|
||||
private int geometrySRID = 4326;
|
||||
|
||||
/**
|
||||
* Set for DB's that support both Sequence and Identity (and the default choice is not desired).
|
||||
*/
|
||||
private IdType idType;
|
||||
|
||||
/**
|
||||
* Setting to indicate if UUID should be stored as binary(16) or varchar(40) or native DB type (for H2 and Postgres).
|
||||
*/
|
||||
private ServerConfig.DbUuid dbUuid = ServerConfig.DbUuid.AUTO_VARCHAR;
|
||||
|
||||
/**
|
||||
* Modify the default mapping of standard types such as default precision for DECIMAL etc.
|
||||
*/
|
||||
private List<CustomDbTypeMapping> customDbTypeMappings = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Return the Geometry SRID.
|
||||
*/
|
||||
public int getGeometrySRID() {
|
||||
return geometrySRID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Geometry SRID.
|
||||
*/
|
||||
public void setGeometrySRID(int geometrySRID) {
|
||||
this.geometrySRID = geometrySRID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB type used to store UUID.
|
||||
*/
|
||||
public ServerConfig.DbUuid getDbUuid() {
|
||||
return dbUuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DB type used to store UUID.
|
||||
*/
|
||||
public void setDbUuid(ServerConfig.DbUuid dbUuid) {
|
||||
this.dbUuid = dbUuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the IdType to use (or null for the default choice).
|
||||
*/
|
||||
public IdType getIdType() {
|
||||
return idType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the IdType to use (when the DB supports both SEQUENCE and IDENTITY and the default is not desired).
|
||||
*/
|
||||
public void setIdType(IdType idType) {
|
||||
this.idType = idType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a custom type mapping.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // set the default mapping for BigDecimal.class/decimal
|
||||
* serverConfig.addCustomMapping(DbType.DECIMAL, "decimal(18,6)");
|
||||
*
|
||||
* // set the default mapping for String.class/varchar but only for Postgres
|
||||
* serverConfig.addCustomMapping(DbType.VARCHAR, "text", Platform.POSTGRES);
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param type The DB type this mapping should apply to
|
||||
* @param columnDefinition The column definition that should be used
|
||||
* @param platform Optionally specify the platform this mapping should apply to.
|
||||
*/
|
||||
public void addCustomMapping(DbType type, String columnDefinition, Platform platform) {
|
||||
customDbTypeMappings.add(new CustomDbTypeMapping(type, columnDefinition, platform));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a custom type mapping that applies to all platforms.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // set the default mapping for BigDecimal/decimal
|
||||
* serverConfig.addCustomMapping(DbType.DECIMAL, "decimal(18,6)");
|
||||
*
|
||||
* // set the default mapping for String/varchar
|
||||
* serverConfig.addCustomMapping(DbType.VARCHAR, "text");
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param type The DB type this mapping should apply to
|
||||
* @param columnDefinition The column definition that should be used
|
||||
*/
|
||||
public void addCustomMapping(DbType type, String columnDefinition) {
|
||||
customDbTypeMappings.add(new CustomDbTypeMapping(type, columnDefinition));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of custom type mappings.
|
||||
*/
|
||||
public List<CustomDbTypeMapping> getCustomTypeMappings() {
|
||||
return customDbTypeMappings;
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ public interface EncryptKeyManager {
|
||||
* This gives the EncryptKeyManager the opportunity to get keys etc.
|
||||
* </p>
|
||||
*/
|
||||
default void initialise() {};
|
||||
default void initialise() {}
|
||||
|
||||
/**
|
||||
* Return the key used to encrypt and decrypt a property mapping to the given
|
||||
|
||||
@@ -31,7 +31,7 @@ public class MatchingNamingConvention extends AbstractNamingConvention {
|
||||
|
||||
@Override
|
||||
public String getColumnFromProperty(Class<?> beanClass, String propertyName) {
|
||||
return propertyName;
|
||||
return quoteIdentifiers(propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -47,8 +47,11 @@ public class MatchingNamingConvention extends AbstractNamingConvention {
|
||||
|
||||
@Override
|
||||
public String getForeignKey(String prefix, String fkProperty) {
|
||||
prefix = databasePlatform.unQuote(prefix);
|
||||
fkProperty = databasePlatform.unQuote(fkProperty);
|
||||
// add fkProperty as init caps
|
||||
return prefix + fkProperty.substring(0, 1).toUpperCase() + fkProperty.substring(1);
|
||||
String fullName = prefix + fkProperty.substring(0, 1).toUpperCase() + fkProperty.substring(1);
|
||||
return quoteIdentifiers(fullName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,335 @@
|
||||
package io.ebean.config;
|
||||
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
import io.ebean.config.dbplatform.IdType;
|
||||
import io.ebean.util.StringHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* Configuration for DB types such as UUID, Geometry etc.
|
||||
*/
|
||||
public class PlatformConfig {
|
||||
|
||||
private boolean allQuotedIdentifiers;
|
||||
|
||||
/**
|
||||
* The database boolean true value (typically either 1, T, or Y).
|
||||
*/
|
||||
private String databaseBooleanTrue;
|
||||
|
||||
/**
|
||||
* The database boolean false value (typically either 0, F or N).
|
||||
*/
|
||||
private String databaseBooleanFalse;
|
||||
|
||||
/**
|
||||
* For DB's using sequences this is the number of sequence values prefetched.
|
||||
*/
|
||||
private int databaseSequenceBatchSize = 20;
|
||||
|
||||
/**
|
||||
* Set for DB's that support both Sequence and Identity (and the default choice is not desired).
|
||||
*/
|
||||
private IdType idType;
|
||||
|
||||
/**
|
||||
* The Geometry SRID value (default 4326).
|
||||
*/
|
||||
private int geometrySRID = 4326;
|
||||
|
||||
/**
|
||||
* Setting to indicate if UUID should be stored as binary(16) or varchar(40) or native DB type (for H2 and Postgres).
|
||||
*/
|
||||
private DbUuid dbUuid = DbUuid.AUTO_VARCHAR;
|
||||
|
||||
/**
|
||||
* Modify the default mapping of standard types such as default precision for DECIMAL etc.
|
||||
*/
|
||||
private List<CustomDbTypeMapping> customDbTypeMappings = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Construct with defaults.
|
||||
*/
|
||||
public PlatformConfig() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct based on given config - typically for DbMigration generation with many platforms.
|
||||
*/
|
||||
public PlatformConfig(PlatformConfig platformConfig) {
|
||||
this.databaseBooleanFalse = platformConfig.databaseBooleanFalse;
|
||||
this.databaseBooleanTrue = platformConfig.databaseBooleanTrue;
|
||||
this.databaseSequenceBatchSize = platformConfig.databaseSequenceBatchSize;
|
||||
this.idType = platformConfig.idType;
|
||||
this.geometrySRID = platformConfig.geometrySRID;
|
||||
this.dbUuid = platformConfig.dbUuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if all DB column and table names should use quoted identifiers.
|
||||
*/
|
||||
public boolean isAllQuotedIdentifiers() {
|
||||
return allQuotedIdentifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if all DB column and table names should use quoted identifiers.
|
||||
*/
|
||||
public void setAllQuotedIdentifiers(boolean allQuotedIdentifiers) {
|
||||
this.allQuotedIdentifiers = allQuotedIdentifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a value used to represent TRUE in the database.
|
||||
* <p>
|
||||
* This is used for databases that do not support boolean natively.
|
||||
* </p>
|
||||
* <p>
|
||||
* The value returned is either a Integer or a String (e.g. "1", or "T").
|
||||
* </p>
|
||||
*/
|
||||
public String getDatabaseBooleanTrue() {
|
||||
return databaseBooleanTrue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value to represent TRUE in the database.
|
||||
* <p>
|
||||
* This is used for databases that do not support boolean natively.
|
||||
* </p>
|
||||
* <p>
|
||||
* The value set is either a Integer or a String (e.g. "1", or "T").
|
||||
* </p>
|
||||
*/
|
||||
public void setDatabaseBooleanTrue(String databaseBooleanTrue) {
|
||||
this.databaseBooleanTrue = databaseBooleanTrue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a value used to represent FALSE in the database.
|
||||
*/
|
||||
public String getDatabaseBooleanFalse() {
|
||||
return databaseBooleanFalse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value used to represent FALSE in the database.
|
||||
*/
|
||||
public void setDatabaseBooleanFalse(String databaseBooleanFalse) {
|
||||
this.databaseBooleanFalse = databaseBooleanFalse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of DB sequence values that should be preallocated.
|
||||
*/
|
||||
public int getDatabaseSequenceBatchSize() {
|
||||
return databaseSequenceBatchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of DB sequence values that should be preallocated.
|
||||
*/
|
||||
public void setDatabaseSequenceBatchSize(int databaseSequenceBatchSize) {
|
||||
this.databaseSequenceBatchSize = databaseSequenceBatchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Geometry SRID.
|
||||
*/
|
||||
public int getGeometrySRID() {
|
||||
return geometrySRID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Geometry SRID.
|
||||
*/
|
||||
public void setGeometrySRID(int geometrySRID) {
|
||||
this.geometrySRID = geometrySRID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB type used to store UUID.
|
||||
*/
|
||||
public DbUuid getDbUuid() {
|
||||
return dbUuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DB type used to store UUID.
|
||||
*/
|
||||
public void setDbUuid(DbUuid dbUuid) {
|
||||
this.dbUuid = dbUuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the IdType to use (or null for the default choice).
|
||||
*/
|
||||
public IdType getIdType() {
|
||||
return idType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the IdType to use (when the DB supports both SEQUENCE and IDENTITY and the default is not desired).
|
||||
*/
|
||||
public void setIdType(IdType idType) {
|
||||
this.idType = idType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a custom type mapping.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // set the default mapping for BigDecimal.class/decimal
|
||||
* serverConfig.addCustomMapping(DbType.DECIMAL, "decimal(18,6)");
|
||||
*
|
||||
* // set the default mapping for String.class/varchar but only for Postgres
|
||||
* serverConfig.addCustomMapping(DbType.VARCHAR, "text", Platform.POSTGRES);
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param type The DB type this mapping should apply to
|
||||
* @param columnDefinition The column definition that should be used
|
||||
* @param platform Optionally specify the platform this mapping should apply to.
|
||||
*/
|
||||
public void addCustomMapping(DbType type, String columnDefinition, Platform platform) {
|
||||
customDbTypeMappings.add(new CustomDbTypeMapping(type, columnDefinition, platform));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a custom type mapping that applies to all platforms.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // set the default mapping for BigDecimal/decimal
|
||||
* serverConfig.addCustomMapping(DbType.DECIMAL, "decimal(18,6)");
|
||||
*
|
||||
* // set the default mapping for String/varchar
|
||||
* serverConfig.addCustomMapping(DbType.VARCHAR, "text");
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param type The DB type this mapping should apply to
|
||||
* @param columnDefinition The column definition that should be used
|
||||
*/
|
||||
public void addCustomMapping(DbType type, String columnDefinition) {
|
||||
customDbTypeMappings.add(new CustomDbTypeMapping(type, columnDefinition));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of custom type mappings.
|
||||
*/
|
||||
public List<CustomDbTypeMapping> getCustomTypeMappings() {
|
||||
return customDbTypeMappings;
|
||||
}
|
||||
|
||||
public void loadSettings(PropertiesWrapper p) {
|
||||
|
||||
idType = p.getEnum(IdType.class, "idType", idType);
|
||||
databaseSequenceBatchSize = p.getInt("databaseSequenceBatchSize", databaseSequenceBatchSize);
|
||||
databaseBooleanTrue = p.get("databaseBooleanTrue", databaseBooleanTrue);
|
||||
databaseBooleanFalse = p.get("databaseBooleanFalse", databaseBooleanFalse);
|
||||
|
||||
DbUuid dbUuid = p.getEnum(DbUuid.class, "dbuuid", null);
|
||||
if (dbUuid != null) {
|
||||
setDbUuid(dbUuid);
|
||||
}
|
||||
if (p.getBoolean("uuidStoreAsBinary", false)) {
|
||||
setDbUuid(DbUuid.BINARY);
|
||||
}
|
||||
|
||||
int srid = p.getInt("geometrySRID", 0);
|
||||
if (srid > 0) {
|
||||
setGeometrySRID(srid);
|
||||
}
|
||||
|
||||
// Mapping is specified in the form: BOOLEAN=int(1);BIT=int(1);
|
||||
String mapping = p.get("mapping");
|
||||
if (mapping != null && !mapping.isEmpty()) {
|
||||
Map<String, String> map = StringHelper.delimitedToMap(mapping, ";", "=");
|
||||
for (Entry<String, String> entry : map.entrySet()) {
|
||||
addCustomMapping(DbType.valueOf(entry.getKey()), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
boolean quotedIdentifiers = p.getBoolean("allQuotedIdentifiers", allQuotedIdentifiers);
|
||||
if (quotedIdentifiers != allQuotedIdentifiers) {
|
||||
// potentially also set to use matching naming convention
|
||||
setAllQuotedIdentifiers(quotedIdentifiers);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify how UUID is stored.
|
||||
*/
|
||||
public enum DbUuid {
|
||||
|
||||
|
||||
/**
|
||||
* Store using native UUID in H2 and Postgres and otherwise fallback to VARCHAR(40).
|
||||
*/
|
||||
AUTO_VARCHAR(true, false, false),
|
||||
|
||||
/**
|
||||
* Store using native UUID in H2 and Postgres and otherwise fallback to BINARY(16).
|
||||
*/
|
||||
AUTO_BINARY(true, true, false),
|
||||
|
||||
/**
|
||||
* Store using native UUID in H2 and Postgres and otherwise fallback to BINARY(16) with optimized packing.
|
||||
*/
|
||||
AUTO_BINARY_OPTIMIZED(true, true, true),
|
||||
|
||||
/**
|
||||
* Store using DB VARCHAR(40).
|
||||
*/
|
||||
VARCHAR(false, false, false),
|
||||
|
||||
/**
|
||||
* Store using DB BINARY(16).
|
||||
*/
|
||||
BINARY(false, true, false),
|
||||
|
||||
/**
|
||||
* Store using DB BINARY(16).
|
||||
*/
|
||||
BINARY_OPTIMIZED(false, true, true);
|
||||
|
||||
boolean nativeType;
|
||||
boolean binary;
|
||||
boolean binaryOptimized;
|
||||
|
||||
DbUuid(boolean nativeType, boolean binary, boolean binaryOptimized) {
|
||||
this.nativeType = nativeType;
|
||||
this.binary = binary;
|
||||
this.binaryOptimized = binaryOptimized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if native UUID type is preferred.
|
||||
*/
|
||||
public boolean useNativeType() {
|
||||
return nativeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if BINARY(16) storage is preferred over VARCHAR(40).
|
||||
*/
|
||||
public boolean useBinary() {
|
||||
return binary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true, if optimized packing should be used.
|
||||
*/
|
||||
public boolean useBinaryOptimized() {
|
||||
return binaryOptimized;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package io.ebean.config;
|
||||
|
||||
/**
|
||||
* Configuration for transaction profiling.
|
||||
*/
|
||||
public class ProfilingConfig {
|
||||
|
||||
/**
|
||||
* When true transaction profiling is enabled.
|
||||
*/
|
||||
private boolean enabled;
|
||||
|
||||
/**
|
||||
* Set true for verbose mode.
|
||||
*/
|
||||
private boolean verbose;
|
||||
|
||||
/**
|
||||
* The minimum transaction execution time to be included in profiling.
|
||||
*/
|
||||
private long minimumMicros;
|
||||
|
||||
/**
|
||||
* A specific set of profileIds to include in profiling.
|
||||
*/
|
||||
private int[] includeProfileIds = {};
|
||||
|
||||
/**
|
||||
* The number of profiles to write per file.
|
||||
*/
|
||||
private long profilesPerFile = 1000;
|
||||
|
||||
private String directory = "profiling";
|
||||
|
||||
/**
|
||||
* Return true if transaction profiling is enabled.
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to enable transaction profiling.
|
||||
*/
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if verbose mode is used.
|
||||
*/
|
||||
public boolean isVerbose() {
|
||||
return verbose;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to use verbose mode.
|
||||
*/
|
||||
public void setVerbose(boolean verbose) {
|
||||
this.verbose = verbose;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the minimum transaction execution to be included in profiling.
|
||||
*/
|
||||
public long getMinimumMicros() {
|
||||
return minimumMicros;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the minimum transaction execution to be included in profiling.
|
||||
*/
|
||||
public void setMinimumMicros(long minimumMicros) {
|
||||
this.minimumMicros = minimumMicros;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the specific set of profileIds to include in profiling.
|
||||
* When not set all transactions with profileIds are included.
|
||||
*/
|
||||
public int[] getIncludeProfileIds() {
|
||||
return includeProfileIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a specific set of profileIds to include in profiling.
|
||||
* When not set all transactions with profileIds are included.
|
||||
*/
|
||||
public void setIncludeProfileIds(int[] includeProfileIds) {
|
||||
this.includeProfileIds = includeProfileIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of profiles to write to a single file.
|
||||
*/
|
||||
public long getProfilesPerFile() {
|
||||
return profilesPerFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of profiles to write to a single file.
|
||||
*/
|
||||
public void setProfilesPerFile(long profilesPerFile) {
|
||||
this.profilesPerFile = profilesPerFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the directory profiling files are put into.
|
||||
*/
|
||||
public String getDirectory() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the directory profiling files are put into.
|
||||
*/
|
||||
public void setDirectory(String directory) {
|
||||
this.directory = directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load setting from properties.
|
||||
*/
|
||||
public void loadSettings(PropertiesWrapper p, String name) {
|
||||
|
||||
enabled = p.getBoolean("profiling", enabled);
|
||||
verbose = p.getBoolean("profiling.verbose", verbose);
|
||||
|
||||
directory = p.get("profiling.directory", directory);
|
||||
profilesPerFile = p.getLong("profiling.profilesPerFile", profilesPerFile);
|
||||
minimumMicros = p.getLong("profiling.minimumMicros", minimumMicros);
|
||||
|
||||
String includeIds = p.get("profiling.includeProfileIds");
|
||||
if (includeIds != null) {
|
||||
includeProfileIds = parseIds(includeIds);
|
||||
}
|
||||
}
|
||||
|
||||
private int[] parseIds(String includeIds) {
|
||||
|
||||
String[] ids = includeIds.split(",");
|
||||
int[] vals = new int[ids.length];
|
||||
for (int i = 0; i < ids.length; i++) {
|
||||
vals[i] = Integer.parseInt(ids[i]);
|
||||
}
|
||||
return vals;
|
||||
}
|
||||
}
|
||||
@@ -10,44 +10,23 @@ public class PropertiesWrapper {
|
||||
|
||||
protected final String serverName;
|
||||
|
||||
protected final PropertyMap propertyMap;
|
||||
private final ClassLoadConfig classLoadConfig;
|
||||
|
||||
/**
|
||||
* Construct with a prefix, serverName and properties.
|
||||
*/
|
||||
public PropertiesWrapper(String prefix, String serverName, Properties properties) {
|
||||
public PropertiesWrapper(String prefix, String serverName, Properties properties, ClassLoadConfig classLoadConfig) {
|
||||
this.serverName = serverName;
|
||||
this.prefix = prefix;
|
||||
this.propertyMap = PropertyMapLoader.load(null, properties);
|
||||
this.properties = propertyMap.asProperties();
|
||||
this.properties = properties;
|
||||
this.classLoadConfig = classLoadConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct without prefix of serverName.
|
||||
*/
|
||||
public PropertiesWrapper(Properties properties) {
|
||||
this(null, null, properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal copy constructor when changing prefix.
|
||||
*/
|
||||
protected PropertiesWrapper(String prefix, String serverName, PropertyMap propertyMap, Properties properties) {
|
||||
this.serverName = serverName;
|
||||
this.prefix = prefix;
|
||||
this.propertyMap = propertyMap;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a PropertiesWrapper instance with a different prefix but same underlying properties.
|
||||
* <p/>
|
||||
* Used when wanting to use "datasource" as the prefix rather than "ebean".
|
||||
* <p/>
|
||||
* The returning instance should only be used in a read only fashion.
|
||||
*/
|
||||
public PropertiesWrapper withPrefix(String prefix) {
|
||||
return new PropertiesWrapper(prefix, serverName, propertyMap, properties);
|
||||
public PropertiesWrapper(Properties properties, ClassLoadConfig classLoadConfig) {
|
||||
this(null, null, properties, classLoadConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,16 +36,6 @@ public class PropertiesWrapper {
|
||||
return serverName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return as Properties with lower case keys and after evaluation and additional properties loading has occurred.
|
||||
* <p>
|
||||
* Ebean has historically ignored the case of keys hence returning the Properties with all the keys lower cased.
|
||||
* </p>
|
||||
*/
|
||||
public Properties asPropertiesLowerCase() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a property with no default value.
|
||||
*/
|
||||
@@ -90,17 +59,21 @@ public class PropertiesWrapper {
|
||||
|
||||
String value = null;
|
||||
if (serverName != null && prefix != null) {
|
||||
value = propertyMap.get(prefix + "." + serverName + "." + key, null);
|
||||
value = internalGet(prefix + "." + serverName + "." + key);
|
||||
}
|
||||
if (value == null && prefix != null) {
|
||||
value = propertyMap.get(prefix + "." + key, null);
|
||||
value = internalGet(prefix + "." + key);
|
||||
}
|
||||
if (value == null) {
|
||||
value = propertyMap.get(key, null);
|
||||
value = internalGet(key);
|
||||
}
|
||||
return value == null ? defaultValue : value;
|
||||
}
|
||||
|
||||
private String internalGet(String key) {
|
||||
return properties.getProperty(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a double property value.
|
||||
*/
|
||||
@@ -145,4 +118,30 @@ public class PropertiesWrapper {
|
||||
return (level == null) ? defaultValue : Enum.valueOf(enumType, level.toUpperCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the instance to use (can be null) for the given plugin.
|
||||
*
|
||||
* @param pluginType the type of plugin
|
||||
* @param key properties key
|
||||
* @param instance existing instance
|
||||
*/
|
||||
public <T> T createInstance(Class<T> pluginType, String key, T instance) {
|
||||
|
||||
if (instance != null) {
|
||||
return instance;
|
||||
}
|
||||
String classname = get(key, null);
|
||||
return createInstance(pluginType, classname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the instance to use (can be null) for the given plugin.
|
||||
*
|
||||
* @param pluginType the type of plugin
|
||||
* @param classname the implementation class as per properties
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T createInstance(Class<T> pluginType, String classname) {
|
||||
return classname == null ? null : (T) classLoadConfig.newInstance(classname);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
package io.ebean.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.naming.InitialContext;
|
||||
import javax.naming.NamingException;
|
||||
|
||||
/**
|
||||
* Helper used to evaluate expressions such as ${CATALINA_HOME}.
|
||||
* <p>
|
||||
* The expressions can contain environment variables, system properties or JNDI
|
||||
* properties. JNDI expressions take the form ${jndi:propertyName} where you
|
||||
* substitute propertyName with the name of the jndi property you wish to
|
||||
* evaluate.
|
||||
* </p>
|
||||
*/
|
||||
final class PropertyExpression {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PropertyExpression.class);
|
||||
|
||||
/**
|
||||
* Prefix for looking up JNDI Environment variable.
|
||||
*/
|
||||
private static final String JAVA_COMP_ENV = "java:comp/env/";
|
||||
|
||||
/**
|
||||
* Used to detect the start of an expression.
|
||||
*/
|
||||
private static final String START = "${";
|
||||
|
||||
/**
|
||||
* Used to detect the end of an expression.
|
||||
*/
|
||||
private static final String END = "}";
|
||||
|
||||
/**
|
||||
* Specify the PropertyHolder.
|
||||
*/
|
||||
private PropertyExpression() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the property value evaluating and replacing any expressions such as
|
||||
* ${CATALINA_HOME}.
|
||||
*/
|
||||
static String eval(String val, PropertyMap map) {
|
||||
if (val == null) {
|
||||
return null;
|
||||
}
|
||||
int sp = val.indexOf(START);
|
||||
if (sp > -1) {
|
||||
int ep = val.indexOf(END, sp + 1);
|
||||
if (ep > -1) {
|
||||
return eval(val, sp, ep, map);
|
||||
}
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the expression using JNDI, Environment variables, System Properties
|
||||
* or existing an property in SystemProperties itself.
|
||||
*/
|
||||
private static String evaluateExpression(String exp, PropertyMap map) {
|
||||
|
||||
if (isJndiExpression(exp)) {
|
||||
// JNDI property lookup...
|
||||
String val = getJndiProperty(exp);
|
||||
if (val != null) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
// check Environment Variables first
|
||||
String val = System.getenv(exp);
|
||||
if (val == null) {
|
||||
// then check system properties
|
||||
val = System.getProperty(exp);
|
||||
}
|
||||
if (val == null && map != null) {
|
||||
// then check PropertyMap
|
||||
val = map.get(exp);
|
||||
}
|
||||
|
||||
if (val != null) {
|
||||
return val;
|
||||
|
||||
} else {
|
||||
// unable to evaluate yet... but maybe later based on the order
|
||||
// in which properties are being set/loaded. You can use
|
||||
// GlobalProperties.evaluateExpressions() to get any unresolved
|
||||
// expressions to be evaluated
|
||||
logger.debug("Unable to evaluate expression [" + exp + "]");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String eval(String val, int sp, int ep, PropertyMap map) {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(val.substring(0, sp));
|
||||
|
||||
String cal = evalExpression(val, sp, ep, map);
|
||||
sb.append(cal);
|
||||
|
||||
eval(val, ep + 1, sb, map);
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void eval(String val, int startPos, StringBuilder sb, PropertyMap map) {
|
||||
if (startPos < val.length()) {
|
||||
int sp = val.indexOf(START, startPos);
|
||||
if (sp > -1) {
|
||||
// append what is between the last token and the new one (if startPos ==
|
||||
// sp nothing gets added)
|
||||
sb.append(val.substring(startPos, sp));
|
||||
int ep = val.indexOf(END, sp + 1);
|
||||
if (ep > -1) {
|
||||
String cal = evalExpression(val, sp, ep, map);
|
||||
sb.append(cal);
|
||||
eval(val, ep + 1, sb, map);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// append what is left...
|
||||
sb.append(val.substring(startPos));
|
||||
}
|
||||
|
||||
private static String evalExpression(String val, int sp, int ep, PropertyMap map) {
|
||||
// trim off start and end ${ and }
|
||||
String exp = val.substring(sp + START.length(), ep);
|
||||
|
||||
// evaluate the variable
|
||||
String evaled = evaluateExpression(exp, map);
|
||||
if (evaled != null) {
|
||||
return evaled;
|
||||
} else {
|
||||
// unable to evaluate at this stage (maybe later)
|
||||
return START + exp + END;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isJndiExpression(String exp) {
|
||||
return exp.startsWith("JNDI:") || exp.startsWith("jndi:");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null if JNDI is not setup or if the property is not found.
|
||||
*
|
||||
* @param key the key of the JNDI Environment property including a JNDI: prefix.
|
||||
*/
|
||||
private static String getJndiProperty(String key) {
|
||||
|
||||
try {
|
||||
// remove the JNDI: prefix
|
||||
key = key.substring(5);
|
||||
|
||||
return (String) getJndiObject(key);
|
||||
|
||||
} catch (NamingException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to getProperty but throws NamingException if JNDI is not setup or
|
||||
* if the property is not found.
|
||||
*/
|
||||
private static Object getJndiObject(String key) throws NamingException {
|
||||
|
||||
InitialContext ctx = new InitialContext();
|
||||
return ctx.lookup(JAVA_COMP_ENV + key);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
package io.ebean.config;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A map like structure of properties.
|
||||
* <p/>
|
||||
* Handles evaluation of expressions like ${home} and provides convenience methods for int, long and boolean.
|
||||
*/
|
||||
public final class PropertyMap implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final LinkedHashMap<String, String> map = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* Return properties loaded from test-ebean.properties.
|
||||
*/
|
||||
public static Properties testProperties() {
|
||||
PropertyMap propertyMap = PropertyMapLoader.loadTestProperties();
|
||||
return (propertyMap == null) ? new Properties() : propertyMap.asProperties();
|
||||
}
|
||||
|
||||
public static Properties defaultProperties() {
|
||||
PropertyMap propertyMap = PropertyMapLoader.loadGlobalProperties();
|
||||
return (propertyMap == null) ? new Properties() : propertyMap.asProperties();
|
||||
}
|
||||
|
||||
public static boolean loadTestProperties() {
|
||||
String disable = System.getProperty("disableTestProperties");
|
||||
return (disable == null || !disable.equalsIgnoreCase("true"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return map.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return as standard Properties.
|
||||
*/
|
||||
public Properties asProperties() {
|
||||
Properties properties = new Properties();
|
||||
for (Entry<String, String> e : entrySet()) {
|
||||
properties.put(e.getKey(), e.getValue());
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Go through all the properties and evaluate any expressions that have not
|
||||
* been resolved.
|
||||
*/
|
||||
public void evaluateProperties() {
|
||||
|
||||
for (Entry<String, String> e : entrySet()) {
|
||||
String key = e.getKey();
|
||||
String val = e.getValue();
|
||||
String eval = eval(val);
|
||||
if (eval != null && !eval.equals(val)) {
|
||||
put(key, eval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value with expressions like ${home} evaluated using system properties and environment variables.
|
||||
*/
|
||||
public synchronized String eval(String val) {
|
||||
return PropertyExpression.eval(val, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the boolean property value with a given default.
|
||||
*/
|
||||
public synchronized boolean getBoolean(String key, boolean defaultValue) {
|
||||
String value = get(key);
|
||||
if (value == null) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
return Boolean.parseBoolean(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the int property value with a given default.
|
||||
*/
|
||||
public synchronized int getInt(String key, int defaultValue) {
|
||||
String value = get(key);
|
||||
if (value == null) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
return Integer.parseInt(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the long property value with a given default.
|
||||
*/
|
||||
public synchronized long getLong(String key, long defaultValue) {
|
||||
String value = get(key);
|
||||
if (value == null) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
return Long.parseLong(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the string property value with a given default.
|
||||
*/
|
||||
public synchronized String get(String key, String defaultValue) {
|
||||
String value = map.get(key.toLowerCase());
|
||||
return value == null ? defaultValue : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the property value returning null if there is no value defined.
|
||||
*/
|
||||
public synchronized String get(String key) {
|
||||
return map.get(key.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Put all evaluating any expressions in the values.
|
||||
*/
|
||||
public synchronized void putEvalAll(Map<String, String> keyValueMap) {
|
||||
|
||||
for (Map.Entry<String, String> entry : keyValueMap.entrySet()) {
|
||||
putEval(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a single key value evaluating any expressions in the value.
|
||||
*/
|
||||
public synchronized String putEval(String key, String value) {
|
||||
value = PropertyExpression.eval(value, this);
|
||||
return map.put(key.toLowerCase(), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a single key value with no expression evaluation.
|
||||
*/
|
||||
public synchronized String put(String key, String value) {
|
||||
return map.put(key.toLowerCase(), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an entry.
|
||||
*/
|
||||
public synchronized String remove(String key) {
|
||||
return map.remove(key.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the entries.
|
||||
*/
|
||||
public synchronized Set<Entry<String, String>> entrySet() {
|
||||
return map.entrySet();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
package io.ebean.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Helper used to load the ebean.properties into a PropertyMap.
|
||||
*/
|
||||
final class PropertyMapLoader {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PropertyMapLoader.class);
|
||||
|
||||
/**
|
||||
* Load the <code>test-ebean.properties</code>.
|
||||
*/
|
||||
public static PropertyMap loadTestProperties() {
|
||||
if (!PropertyMap.loadTestProperties()) {
|
||||
return new PropertyMap();
|
||||
}
|
||||
return load(null, "test-ebean.properties");
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the ebean.properties (and test-ebean.properties if present).
|
||||
*/
|
||||
public static PropertyMap loadGlobalProperties() {
|
||||
|
||||
boolean loadTestProperties = false;
|
||||
String fileName = System.getenv("EBEAN_PROPS_FILE");
|
||||
if (fileName == null) {
|
||||
fileName = System.getProperty("ebean.props.file");
|
||||
if (fileName == null) {
|
||||
loadTestProperties = PropertyMap.loadTestProperties();
|
||||
fileName = "ebean.properties";
|
||||
}
|
||||
}
|
||||
|
||||
PropertyMap map = load(null, fileName);
|
||||
if (loadTestProperties) {
|
||||
// load test properties if present in classpath
|
||||
map = load(map, "test-ebean.properties");
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the file returning the property map.
|
||||
*
|
||||
* @param p an existing property map to load into.
|
||||
* @param fileName the name of the properties file to load.
|
||||
*/
|
||||
public static PropertyMap load(PropertyMap p, String fileName) {
|
||||
|
||||
InputStream is = findInputStream(fileName);
|
||||
if (is == null) {
|
||||
return p;
|
||||
} else {
|
||||
return load(p, is);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the InputStream returning the property map.
|
||||
*
|
||||
* @param p an existing property map to load into.
|
||||
* @param in the InputStream of the properties file to load.
|
||||
*/
|
||||
public static PropertyMap load(PropertyMap p, InputStream in) {
|
||||
|
||||
Properties props = new Properties();
|
||||
try {
|
||||
props.load(in);
|
||||
in.close();
|
||||
return load(p, props);
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static PropertyMap load(PropertyMap p, Properties props) {
|
||||
|
||||
if (p == null) {
|
||||
p = new PropertyMap();
|
||||
}
|
||||
|
||||
// put values in initially without any evaluation
|
||||
for (Map.Entry<Object, Object> entry : props.entrySet()) {
|
||||
String key = ((String) entry.getKey()).toLowerCase();
|
||||
String val = ((String) entry.getValue());
|
||||
if (val != null) {
|
||||
val = val.trim();
|
||||
}
|
||||
p.put(key, val);
|
||||
}
|
||||
|
||||
p.evaluateProperties();
|
||||
|
||||
String otherProps = p.remove("load.properties");
|
||||
if (otherProps == null) {
|
||||
otherProps = p.remove("load.properties.override");
|
||||
}
|
||||
if (otherProps != null) {
|
||||
otherProps = otherProps.replace("\\", "/");
|
||||
InputStream is = findInputStream(otherProps);
|
||||
if (is != null) {
|
||||
logger.debug("loading properties from " + otherProps);
|
||||
load(p, is);
|
||||
} else {
|
||||
logger.error("load.properties " + otherProps + " not found.");
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the input stream given the file name.
|
||||
*/
|
||||
private static InputStream findInputStream(String fileName) {
|
||||
|
||||
if (fileName == null) {
|
||||
throw new NullPointerException("fileName is null?");
|
||||
}
|
||||
|
||||
try {
|
||||
File f = new File(fileName);
|
||||
|
||||
if (f.exists()) {
|
||||
logger.debug(fileName + " found in file system");
|
||||
return new FileInputStream(f);
|
||||
} else {
|
||||
InputStream in = findInClassPath(fileName);
|
||||
if (in != null) {
|
||||
logger.debug(fileName + " found in classpath");
|
||||
}
|
||||
return in;
|
||||
}
|
||||
|
||||
} catch (FileNotFoundException ex) {
|
||||
// already made the check so this
|
||||
// should never be thrown
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static InputStream findInClassPath(String fileName) {
|
||||
return Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,27 +1,24 @@
|
||||
package io.ebean.config;
|
||||
|
||||
/**
|
||||
* Matches the functionality of javax.persistence.AttributeConverter
|
||||
* <p>
|
||||
* In general AttributeConverter should be used in preference to this
|
||||
* ScalarTypeConverter as it is JPA standard and offers the same functionality.
|
||||
* </p>
|
||||
* <p>
|
||||
* For Ebean we will look to deprecate this interface in preference to AttributeConverter.
|
||||
* </p>
|
||||
* <p>
|
||||
* Used to convert between a value object and a known scalar type. The value
|
||||
* object is the logical type used in your application and the scalar type is
|
||||
* the value used to persist than to the DB.
|
||||
* </p>
|
||||
* <p>
|
||||
* The Value object should be immutable and scalar (aka not compound) and
|
||||
* converts to and from a known scalar type which Ebean will use to persist the
|
||||
* value.
|
||||
* </p>
|
||||
* <p>
|
||||
* This is an easier alternative to implementing the
|
||||
* io.ebean.server.type.ScalarType interface.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that Ebean will automatically try to detect Immutable Scalar Value
|
||||
* Objects and automatically support them via reflection. This however would not
|
||||
* be appropriate when the logical type is different from the type you wish to
|
||||
* use for persistence - for example, if the logical type was long and you
|
||||
* wanted to use java.sql.Timestamp for persistence. In this case you would want
|
||||
* to implement this interface rather than let Ebean automatically support that
|
||||
* type via reflection.
|
||||
* </p>
|
||||
*
|
||||
* @param <B> The value object type.
|
||||
* @param <S> The scalar object type that is used to persist the value object.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,39 @@
|
||||
package io.ebean.config;
|
||||
|
||||
/**
|
||||
* Provides a ServiceLoader based mechanism to configure a ServerConfig.
|
||||
* <p>
|
||||
* Provide an implementation and register it via the standard Java ServiceLoader mechanism
|
||||
* via a file at <code>META-INF/services/io.ebean.config.ServerConfigProvider</code>.
|
||||
* </p>
|
||||
* <p>
|
||||
* If you are using a DI container like Spring or Guice you are unlikely to use this but instead use a
|
||||
* spring specific configuration. When we are not using a DI container we may use this mechanism to
|
||||
* explicitly register the entity beans and avoid classpath scanning.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* public class EbeanConfigProvider implements ServerConfigProvider {
|
||||
*
|
||||
* @Override
|
||||
* public void apply(ServerConfig config) {
|
||||
*
|
||||
* // register the entity bean classes explicitly
|
||||
* config.addClass(Customer.class);
|
||||
* config.addClass(User.class);
|
||||
* ...
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public interface ServerConfigProvider {
|
||||
|
||||
/**
|
||||
* Apply the configuration to the ServerConfig.
|
||||
* <p>
|
||||
* Typically we explicitly register entity bean classes and thus avoid classpath scanning.
|
||||
* </p>
|
||||
*/
|
||||
void apply(ServerConfig config);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package io.ebean.config;
|
||||
|
||||
import io.ebean.bean.ObjectGraphNode;
|
||||
|
||||
/**
|
||||
* Slow query event.
|
||||
*/
|
||||
public class SlowQueryEvent {
|
||||
|
||||
private final String sql;
|
||||
|
||||
private final long timeMillis;
|
||||
|
||||
private final int rowCount;
|
||||
|
||||
private final ObjectGraphNode originNode;
|
||||
|
||||
/**
|
||||
* Construct with the SQL and execution time in millis.
|
||||
*/
|
||||
public SlowQueryEvent(String sql, long timeMillis, int rowCount, ObjectGraphNode originNode) {
|
||||
this.sql = sql;
|
||||
this.timeMillis = timeMillis;
|
||||
this.rowCount = rowCount;
|
||||
this.originNode = originNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SQL for the slow query.
|
||||
*/
|
||||
public String getSql() {
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the execution time in millis.
|
||||
*/
|
||||
public long getTimeMillis() {
|
||||
return timeMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the total row count associated with the query.
|
||||
*/
|
||||
public int getRowCount() {
|
||||
return rowCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the origin point for the root query.
|
||||
* <p>
|
||||
* Typically the <code>originNode.getOriginQueryPoint().getFirstStackElement()</code> provides the stack line that
|
||||
* shows the code that invoked the query.
|
||||
* </p>
|
||||
*/
|
||||
public ObjectGraphNode getOriginNode() {
|
||||
return originNode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package io.ebean.config;
|
||||
|
||||
/**
|
||||
* Listener for slow query events.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface SlowQueryListener {
|
||||
|
||||
/**
|
||||
* Process a slow query event.
|
||||
*/
|
||||
void process(SlowQueryEvent event);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package io.ebean.config;
|
||||
|
||||
/**
|
||||
* For multi-tenancy via DB CATALOG supply the catalog given the tenantId.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface TenantCatalogProvider {
|
||||
|
||||
/**
|
||||
* Return the DB catalog for the given tenantId.
|
||||
*
|
||||
* @param tenantId The current tenant id.
|
||||
* @return The DB catalog to use for the given tenant
|
||||
*/
|
||||
String catalog(Object tenantId);
|
||||
}
|
||||
@@ -16,5 +16,5 @@ public interface TenantDataSourceProvider {
|
||||
/**
|
||||
* Shutdown all the DataSources.
|
||||
*/
|
||||
default void shutdown(boolean deregisterDriver) {};
|
||||
default void shutdown(boolean deregisterDriver) {}
|
||||
}
|
||||
|
||||
@@ -8,27 +8,40 @@ public enum TenantMode {
|
||||
/**
|
||||
* No multi-tenancy.
|
||||
*/
|
||||
NONE(false),
|
||||
NONE(false, true),
|
||||
|
||||
/**
|
||||
* Each Tenant has their own Database (javax.sql.DataSource)
|
||||
*/
|
||||
DB(true),
|
||||
DB(true, false),
|
||||
|
||||
/**
|
||||
* Each Tenant has their own Database schema.
|
||||
*/
|
||||
SCHEMA(true),
|
||||
SCHEMA(true, false),
|
||||
|
||||
/**
|
||||
* Each Tenant has their own Database but with in connection pool
|
||||
*/
|
||||
CATALOG(true, false),
|
||||
|
||||
/**
|
||||
* Tenants share tables but have a discriminator/partition column that partitions the data.
|
||||
*/
|
||||
PARTITION(false);
|
||||
PARTITION(false, true),
|
||||
|
||||
/**
|
||||
* Each Tenant has their own Database (javax.sql.DataSource), and there is also one master-database
|
||||
* (that holds configuration e.g.)
|
||||
*/
|
||||
DB_WITH_MASTER(true, true);
|
||||
|
||||
boolean dynamicDataSource;
|
||||
boolean ddlEnabled;
|
||||
|
||||
TenantMode(boolean dynamicDataSource) {
|
||||
TenantMode(boolean dynamicDataSource, boolean ddlEnabled) {
|
||||
this.dynamicDataSource = dynamicDataSource;
|
||||
this.ddlEnabled = ddlEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,4 +50,12 @@ public enum TenantMode {
|
||||
public boolean isDynamicDataSource() {
|
||||
return dynamicDataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true, if DDL is enabled.
|
||||
*/
|
||||
public boolean isDdlEnabled() {
|
||||
return ddlEnabled;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -112,39 +112,7 @@ public class UnderscoreNamingConvention extends AbstractNamingConvention {
|
||||
* Convert and return the string to underscore from camel case.
|
||||
*/
|
||||
protected String toUnderscoreFromCamel(String camelCase) {
|
||||
|
||||
int lastUpper = -1;
|
||||
StringBuilder sb = new StringBuilder(camelCase.length() + 4);
|
||||
for (int i = 0; i < camelCase.length(); i++) {
|
||||
char c = camelCase.charAt(i);
|
||||
|
||||
if ('_' == c) {
|
||||
// Underscores should just be passed through
|
||||
sb.append(c);
|
||||
lastUpper = i;
|
||||
} else if (Character.isDigit(c)) {
|
||||
if (i > lastUpper + 1 && !digitsCompressed) {
|
||||
sb.append("_");
|
||||
}
|
||||
sb.append(c);
|
||||
lastUpper = i;
|
||||
|
||||
} else if (Character.isUpperCase(c)) {
|
||||
if (i > lastUpper + 1) {
|
||||
sb.append("_");
|
||||
}
|
||||
sb.append(Character.toLowerCase(c));
|
||||
lastUpper = i;
|
||||
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
String ret = sb.toString();
|
||||
if (forceUpperCase) {
|
||||
ret = ret.toUpperCase();
|
||||
}
|
||||
return ret;
|
||||
return CamelCaseHelper.toUnderscoreFromCamel(camelCase, digitsCompressed, forceUpperCase);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,13 +2,11 @@ package io.ebean.config.dbplatform;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.annotation.PersistBatch;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.CustomDbTypeMapping;
|
||||
import io.ebean.config.DbTypeConfig;
|
||||
import io.ebean.PersistBatch;
|
||||
import io.ebean.Platform;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.dbmigration.ddlgeneration.DdlHandler;
|
||||
import io.ebean.dbmigration.ddlgeneration.platform.PlatformDdl;
|
||||
import io.ebean.config.PlatformConfig;
|
||||
import io.ebean.util.JdbcClose;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -18,6 +16,7 @@ import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
@@ -64,6 +63,13 @@ public class DatabasePlatform {
|
||||
*/
|
||||
protected String closeQuote = "\"";
|
||||
|
||||
protected String concatOperator = "||";
|
||||
|
||||
/**
|
||||
* When set to true all db column names and table names use quoted identifiers.
|
||||
*/
|
||||
protected boolean allQuotedIdentifiers;
|
||||
|
||||
/**
|
||||
* For limit/offset, row_number etc limiting of SQL queries.
|
||||
*/
|
||||
@@ -94,6 +100,10 @@ public class DatabasePlatform {
|
||||
*/
|
||||
protected DbIdentity dbIdentity = new DbIdentity();
|
||||
|
||||
protected boolean sequenceBatchMode = true;
|
||||
|
||||
protected int sequenceBatchSize = 20;
|
||||
|
||||
/**
|
||||
* The history support for this database platform.
|
||||
*/
|
||||
@@ -136,9 +146,25 @@ public class DatabasePlatform {
|
||||
private static final char BACK_TICK = '`';
|
||||
|
||||
/**
|
||||
* The like clause. Can be overridden to disable default escape character.
|
||||
* The non-escaped like clause (to stop slash being escaped on some platforms).
|
||||
* Used for the 'raw like' expression but not for startsWith, endsWith and contains expressions.
|
||||
*/
|
||||
protected String likeClause = "like ?";
|
||||
protected String likeClauseRaw = "like ? escape''";
|
||||
|
||||
/**
|
||||
* Escaped like clause for startsWith, endsWith and contains.
|
||||
*/
|
||||
protected String likeClauseEscaped = "like ? escape'|'";
|
||||
|
||||
/**
|
||||
* Escape character used for startsWith, endsWith and contains.
|
||||
*/
|
||||
protected char likeEscapeChar = '|';
|
||||
|
||||
/**
|
||||
* Characters escaped for startsWith, endsWith and contains.
|
||||
*/
|
||||
protected char[] likeSpecialCharacters = { '%', '_', '|' };
|
||||
|
||||
protected DbEncrypt dbEncrypt;
|
||||
|
||||
@@ -157,8 +183,6 @@ public class DatabasePlatform {
|
||||
*/
|
||||
protected PersistBatch persistBatchOnCascade = PersistBatch.ALL;
|
||||
|
||||
protected PlatformDdl platformDdl;
|
||||
|
||||
/**
|
||||
* The maximum length of table names - used specifically when derived
|
||||
* default table names for intersection tables.
|
||||
@@ -188,10 +212,20 @@ public class DatabasePlatform {
|
||||
return exceptionTranslator.translate(message, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the platform given the server configuration.
|
||||
*/
|
||||
public void configure(PlatformConfig config) {
|
||||
this.sequenceBatchSize = config.getDatabaseSequenceBatchSize();
|
||||
configureIdType(config.getIdType());
|
||||
configure(config, config.isAllQuotedIdentifiers());
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure UUID Storage etc based on ServerConfig settings.
|
||||
*/
|
||||
public void configure(DbTypeConfig config) {
|
||||
protected void configure(PlatformConfig config, boolean allQuotedIdentifiers) {
|
||||
this.allQuotedIdentifiers = allQuotedIdentifiers;
|
||||
addGeoTypes(config.getGeometrySRID());
|
||||
configureIdType(config.getIdType());
|
||||
dbTypeMap.config(nativeUuidType, config.getDbUuid());
|
||||
@@ -224,7 +258,7 @@ public class DatabasePlatform {
|
||||
* Return true if this matches the given platform.
|
||||
*/
|
||||
public boolean isPlatform(Platform platform) {
|
||||
return this.platform.equals(platform);
|
||||
return this.platform == platform;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -244,6 +278,20 @@ public class DatabasePlatform {
|
||||
return platform.name().toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if we are using Sequence batch mode rather than STEP.
|
||||
*/
|
||||
public boolean isSequenceBatchMode() {
|
||||
return sequenceBatchMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to false to not use sequence batch mode but instead STEP mode.
|
||||
*/
|
||||
public void setSequenceBatchMode(boolean sequenceBatchMode) {
|
||||
this.sequenceBatchMode = sequenceBatchMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this database platform supports native ILIKE expression.
|
||||
*/
|
||||
@@ -268,20 +316,6 @@ public class DatabasePlatform {
|
||||
return maxConstraintNameLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the platform specific DDL.
|
||||
*/
|
||||
public PlatformDdl getPlatformDdl() {
|
||||
return platformDdl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a DDL handler for generating DDL scripts.
|
||||
*/
|
||||
public DdlHandler createDdlHandler(ServerConfig serverConfig) {
|
||||
return platformDdl.createDdlHandler(serverConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the JDBC driver does not allow additional queries to execute
|
||||
* when a resultSet is being 'streamed' as is the case with findEach() etc.
|
||||
@@ -299,10 +333,10 @@ public class DatabasePlatform {
|
||||
* @param be the BackgroundExecutor that can be used to load the sequence if
|
||||
* desired
|
||||
* @param ds the DataSource
|
||||
* @param stepSize the sequence allocation size as defined by mapping (defaults to 50)
|
||||
* @param seqName the name of the sequence
|
||||
* @param batchSize the number of sequences that should be loaded
|
||||
*/
|
||||
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
|
||||
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, int stepSize, String seqName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -348,6 +382,13 @@ public class DatabasePlatform {
|
||||
this.historySupport = historySupport;
|
||||
}
|
||||
|
||||
/**
|
||||
* So no except for Postgres and CockroachDB.
|
||||
*/
|
||||
public boolean isNativeArrayType() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the DB supports native UUID.
|
||||
*/
|
||||
@@ -401,8 +442,6 @@ public class DatabasePlatform {
|
||||
|
||||
/**
|
||||
* Return the close quote for quoted identifiers.
|
||||
*
|
||||
* @return the close quote
|
||||
*/
|
||||
public String getCloseQuote() {
|
||||
return closeQuote;
|
||||
@@ -410,17 +449,20 @@ public class DatabasePlatform {
|
||||
|
||||
/**
|
||||
* Return the open quote for quoted identifiers.
|
||||
*
|
||||
* @return the open quote
|
||||
*/
|
||||
public String getOpenQuote() {
|
||||
return openQuote;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB concat operator.
|
||||
*/
|
||||
public String getConcatOperator() {
|
||||
return concatOperator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JDBC type used to store booleans.
|
||||
*
|
||||
* @return the boolean db type
|
||||
*/
|
||||
public int getBooleanDbType() {
|
||||
return booleanDbType;
|
||||
@@ -483,6 +525,13 @@ public class DatabasePlatform {
|
||||
this.forwardOnlyHintOnFindIterate = forwardOnlyHintOnFindIterate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normally not needed - overridden in CockroachPlatform.
|
||||
*/
|
||||
public boolean isDdlCommitOnCreateIndex() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB identity/sequence features for this platform.
|
||||
*
|
||||
@@ -533,24 +582,33 @@ public class DatabasePlatform {
|
||||
* naming rules.
|
||||
* </p>
|
||||
*
|
||||
* @param dbName the db name
|
||||
* @return the string
|
||||
* @param dbName the db table or column name
|
||||
* @return the db table or column name with potentially platform specific quoted identifiers
|
||||
*/
|
||||
public String convertQuotedIdentifiers(String dbName) {
|
||||
// Ignore null values e.g. schema name or catalog
|
||||
if (dbName != null && !dbName.isEmpty()) {
|
||||
if (dbName.charAt(0) == BACK_TICK) {
|
||||
if (dbName.charAt(dbName.length() - 1) == BACK_TICK) {
|
||||
|
||||
String quotedName = getOpenQuote();
|
||||
quotedName += dbName.substring(1, dbName.length() - 1);
|
||||
quotedName += getCloseQuote();
|
||||
|
||||
return quotedName;
|
||||
|
||||
return openQuote + dbName.substring(1, dbName.length() - 1) + closeQuote;
|
||||
} else {
|
||||
logger.error("Missing backquote on [" + dbName + "]");
|
||||
}
|
||||
} else if (allQuotedIdentifiers) {
|
||||
return openQuote + dbName + closeQuote;
|
||||
}
|
||||
}
|
||||
return dbName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove quoted identifier quotes from the table or column name if present.
|
||||
*/
|
||||
public String unQuote(String dbName) {
|
||||
if (dbName != null && !dbName.isEmpty()) {
|
||||
if (dbName.startsWith(openQuote)) {
|
||||
// trim off the open and close quotes
|
||||
return dbName.substring(1, dbName.length()-1);
|
||||
}
|
||||
}
|
||||
return dbName;
|
||||
@@ -571,6 +629,14 @@ public class DatabasePlatform {
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* For update hint on the FROM clause (SQL server only).
|
||||
*/
|
||||
public String fromForUpdate(Query.ForUpdate forUpdateMode) {
|
||||
// return null except for sql server
|
||||
return null;
|
||||
}
|
||||
|
||||
protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode) {
|
||||
// silently assume the database does not support the "for update" clause.
|
||||
logger.info("it seems your database does not support the 'for update' clause");
|
||||
@@ -582,8 +648,8 @@ public class DatabasePlatform {
|
||||
* <p>
|
||||
* This may include an escape clause to disable a default escape character.
|
||||
*/
|
||||
public String getLikeClause() {
|
||||
return likeClause;
|
||||
public String getLikeClause(boolean rawLikeExpression) {
|
||||
return rawLikeExpression ? likeClauseRaw : likeClauseEscaped;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -593,6 +659,39 @@ public class DatabasePlatform {
|
||||
return persistBatchOnCascade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the DB schema if it does not exist.
|
||||
*/
|
||||
public void createSchemaIfNotExists(String dbSchema, Connection connection) throws SQLException {
|
||||
if (!schemaExists(dbSchema, connection)) {
|
||||
Statement query = connection.createStatement();
|
||||
try {
|
||||
logger.info("create schema:{}", dbSchema);
|
||||
query.executeUpdate("create schema " + dbSchema);
|
||||
} finally {
|
||||
JdbcClose.close(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the schema exists.
|
||||
*/
|
||||
public boolean schemaExists(String dbSchema, Connection connection) throws SQLException {
|
||||
ResultSet schemas = connection.getMetaData().getSchemas();
|
||||
try {
|
||||
while (schemas.next()) {
|
||||
String schema = schemas.getString(1);
|
||||
if (schema.equalsIgnoreCase(dbSchema)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
JdbcClose.close(schemas);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the table exists.
|
||||
*/
|
||||
@@ -603,18 +702,40 @@ public class DatabasePlatform {
|
||||
try {
|
||||
return tables.next();
|
||||
} finally {
|
||||
close(tables);
|
||||
JdbcClose.close(tables);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the resultSet.
|
||||
* Escapes the like string for this DB-Platform
|
||||
*/
|
||||
protected void close(ResultSet resultSet) {
|
||||
try {
|
||||
resultSet.close();
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error closing resultSet", e);
|
||||
public String escapeLikeString(String value) {
|
||||
StringBuilder sb = null;
|
||||
for (int i = 0; i < value.length(); i++) {
|
||||
char ch = value.charAt(i);
|
||||
boolean escaped = false;
|
||||
for (char escapeChar: likeSpecialCharacters) {
|
||||
if (ch == escapeChar) {
|
||||
if (sb == null) {
|
||||
sb = new StringBuilder(value.substring(0, i));
|
||||
}
|
||||
escapeLikeCharacter(escapeChar, sb);
|
||||
escaped = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!escaped && sb != null) {
|
||||
sb.append(ch);
|
||||
}
|
||||
}
|
||||
if (sb == null) {
|
||||
return value;
|
||||
} else {
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
|
||||
sb.append(likeEscapeChar).append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package io.ebean.config.dbplatform;
|
||||
|
||||
import java.sql.Types;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
|
||||
import io.ebean.annotation.DbDefault;
|
||||
|
||||
/**
|
||||
* DB Column default values mapping to database platform specific literals.
|
||||
*/
|
||||
@@ -22,6 +27,13 @@ public class DbDefaultValue {
|
||||
* The key for the NOW / current timestamp.
|
||||
*/
|
||||
public static final String NOW = "now";
|
||||
|
||||
/**
|
||||
* The 'null' literal.
|
||||
*/
|
||||
public static final String NULL = "null";
|
||||
|
||||
|
||||
|
||||
protected Map<String, String> map = new LinkedHashMap<>();
|
||||
|
||||
@@ -63,8 +75,128 @@ public class DbDefaultValue {
|
||||
if (dbDefaultLiteral == null) {
|
||||
return null;
|
||||
}
|
||||
if (dbDefaultLiteral.startsWith("$RAW:")) {
|
||||
return dbDefaultLiteral.substring(5);
|
||||
}
|
||||
String val = map.get(dbDefaultLiteral);
|
||||
return val != null ? val : dbDefaultLiteral;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method checks & convert the {@link DbDefault#value()} to a valid SQL literal.
|
||||
*
|
||||
* This is mainly to quote string literals and verify integer/dates for correctness.
|
||||
* <p>
|
||||
* Note: There are some special cases:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>Normal Quoting: <code>@DbDefault("User's default")</code> on a String propery
|
||||
* returns: <code>default 'User''s default'</code><br/>
|
||||
* (the same on an integer property will throw a NumberFormatException)</li>
|
||||
* <li>Special case null: <code>@DbDefault("null")</code> will return this: <code>default null</code><br/>
|
||||
* If you need really the String "null", you have to specify <code>@DbDefault("'null'")</code>
|
||||
* which gives you the <code>default 'null'</code> statement.</li>
|
||||
* <li>Any statement, that begins and ends with single quote will not be checked or get quoted again.</li>
|
||||
* <li>A statement that begins with "$RAW:", e.g <code>@DbDefault("$RAW:N'SANDNES'")</code> will lead to
|
||||
* a <code>default N'SANDNES'</code> in DDL. Note that this is platform specific!</li>
|
||||
* </ul>
|
||||
*/
|
||||
public static String toSqlLiteral(String defaultValue, Class<?> propertyType, int sqlType) {
|
||||
if (propertyType == null
|
||||
|| defaultValue == null
|
||||
|| NULL.equals(defaultValue)
|
||||
|| (defaultValue.startsWith("'") && defaultValue.endsWith("'"))
|
||||
|| (defaultValue.startsWith("$RAW:"))) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
if (Boolean.class.isAssignableFrom(propertyType) || Boolean.TYPE.isAssignableFrom(propertyType)) {
|
||||
return toBooleanLiteral(defaultValue);
|
||||
}
|
||||
|
||||
if (Number.class.isAssignableFrom(propertyType)
|
||||
|| Byte.TYPE.equals(propertyType)
|
||||
|| Short.TYPE.equals(propertyType)
|
||||
|| Integer.TYPE.equals(propertyType)
|
||||
|| Long.TYPE.equals(propertyType)
|
||||
|| Float.TYPE.equals(propertyType)
|
||||
|| Double.TYPE.equals(propertyType)
|
||||
|| (propertyType.isEnum() && sqlType == Types.INTEGER)) {
|
||||
Double.valueOf(defaultValue); // verify if it is a number
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// check if it is a date/time - in all other cases return quoted defaultValue
|
||||
switch (sqlType) {
|
||||
// date
|
||||
case Types.DATE:
|
||||
return toDateLiteral(defaultValue);
|
||||
// time
|
||||
case Types.TIME:
|
||||
case Types.TIME_WITH_TIMEZONE:
|
||||
return toTimeLiteral(defaultValue);
|
||||
// timestamp
|
||||
case Types.TIMESTAMP:
|
||||
case Types.TIMESTAMP_WITH_TIMEZONE:
|
||||
return toDateTimeLiteral(defaultValue);
|
||||
|
||||
default:
|
||||
return toTextLiteral(defaultValue); // do not check other datatypes
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if specified value is either 'true' or 'false'. The literal is translated later.
|
||||
*/
|
||||
private static String toBooleanLiteral(String value) {
|
||||
if (DbDefaultValue.FALSE.equals(value) || DbDefaultValue.TRUE.equals(value)) {
|
||||
return value;
|
||||
}
|
||||
throw new IllegalArgumentException("'" + value + "' is not a valid value for boolean");
|
||||
}
|
||||
|
||||
/**
|
||||
* This adds single qoutes around the <code>value</code> and doubles single quotes.
|
||||
* "User's home" will return "'User''s home'"
|
||||
*/
|
||||
private static String toTextLiteral(String value) {
|
||||
StringBuilder sb = new StringBuilder(value.length()+10);
|
||||
sb.append('\'');
|
||||
for (int i = 0; i < value.length(); i++) {
|
||||
char ch = value.charAt(i);
|
||||
if (ch == '\'') {
|
||||
sb.append("''");
|
||||
} else {
|
||||
sb.append(ch);
|
||||
}
|
||||
}
|
||||
sb.append('\'');
|
||||
return sb.toString();
|
||||
|
||||
}
|
||||
|
||||
private static String toDateLiteral(String value) {
|
||||
if (NOW.equals(value)) {
|
||||
return value; // this will get translated later
|
||||
}
|
||||
DatatypeConverter.parseDate(value); // verify
|
||||
return toTextLiteral(value);
|
||||
}
|
||||
|
||||
private static String toTimeLiteral(String value) {
|
||||
if (NOW.equals(value)) {
|
||||
return value; // this will get translated later
|
||||
}
|
||||
DatatypeConverter.parseTime(value); // verify
|
||||
return toTextLiteral(value);
|
||||
}
|
||||
|
||||
private static String toDateTimeLiteral(String value) {
|
||||
if (NOW.equals(value)) {
|
||||
return value; // this will get translated later
|
||||
}
|
||||
DatatypeConverter.parseDateTime(value); // verify
|
||||
return toTextLiteral(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package io.ebean.config.dbplatform;
|
||||
|
||||
import io.ebean.dbmigration.migration.IdentityType;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Defines the identity/sequence behaviour for the database.
|
||||
*/
|
||||
public class DbIdentity {
|
||||
|
||||
private static final Pattern TABLE_REPLACE = Pattern.compile("{table}", Pattern.LITERAL);
|
||||
|
||||
/**
|
||||
* Set if this DB supports sequences. Note some DB's support both Sequences
|
||||
* and Identity.
|
||||
@@ -50,7 +53,8 @@ public class DbIdentity {
|
||||
if (selectLastInsertedIdTemplate == null) {
|
||||
return null;
|
||||
}
|
||||
return selectLastInsertedIdTemplate.replace("{table}", table);
|
||||
|
||||
return TABLE_REPLACE.matcher(selectLastInsertedIdTemplate).replaceAll(Matcher.quoteReplacement(table));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,28 +120,4 @@ public class DbIdentity {
|
||||
this.idType = idType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the id type to use based on requested identityType and
|
||||
* the support for that in the database platform.
|
||||
*/
|
||||
public IdType useIdentityType(IdentityType identityType) {
|
||||
|
||||
if (identityType == null) {
|
||||
// use the default
|
||||
return idType;
|
||||
}
|
||||
switch (identityType) {
|
||||
case GENERATOR:
|
||||
return IdType.GENERATOR;
|
||||
case EXTERNAL:
|
||||
return IdType.EXTERNAL;
|
||||
case SEQUENCE:
|
||||
return supportsSequence ? IdType.SEQUENCE : idType;
|
||||
case IDENTITY:
|
||||
return supportsIdentity ? IdType.IDENTITY : idType;
|
||||
default:
|
||||
return idType;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user