├── .gitignore ├── LICENSE ├── NOTICE ├── README.md ├── images ├── 2023-11-14-001.png ├── 2023-11-14-005.png ├── 2023-11-14-017.png ├── 2023-11-14-020.png ├── 2023-11-14-021.png ├── 2023-11-14-022.png ├── 2023-11-14-023.png ├── 2023-11-14-024.png ├── 2023-11-14-025.png ├── 2023-11-14-026.png ├── bigdata.png ├── concurrent-001.jpg ├── concurrent-002.png ├── concurrent-003.jpg ├── gongzhonghao.png ├── hacker_binghe.jpg ├── ice_video.png ├── ice_wechat.jpg ├── mysql.png ├── transaction.png ├── xingqiu.png └── xingqiu_149.png ├── pom.xml └── src ├── main ├── java │ └── io │ │ └── binghe │ │ └── redis │ │ ├── SpringRedisStarter.java │ │ ├── cache │ │ ├── distribute │ │ │ ├── DistributeCacheService.java │ │ │ ├── conversion │ │ │ │ └── TypeConversion.java │ │ │ ├── data │ │ │ │ └── RedisData.java │ │ │ └── redis │ │ │ │ └── RedisDistributeCacheService.java │ │ └── local │ │ │ ├── LocalCacheService.java │ │ │ └── guava │ │ │ ├── factoty │ │ │ └── LocalGuavaCacheFactory.java │ │ │ └── impl │ │ │ └── GuavaLocalCacheService.java │ │ ├── config │ │ ├── RedisPoolConfig.java │ │ └── RedissonConfig.java │ │ ├── lock │ │ ├── DistributedLock.java │ │ ├── factory │ │ │ └── DistributedLockFactory.java │ │ └── redisson │ │ │ └── RedissonLockFactory.java │ │ ├── semaphore │ │ ├── DistributedSemaphore.java │ │ ├── factory │ │ │ └── DistributedSemaphoreFactory.java │ │ └── redisson │ │ │ └── RedissonSemaphoreFactory.java │ │ └── utils │ │ └── ThreadPoolUtils.java └── resources │ └── application.yml └── test └── java └── io └── binghe └── redis └── test ├── DistributeCacheServiceTest.java └── bean └── User.java /.gitignore: -------------------------------------------------------------------------------- 1 | # maven ignore 2 | target/ 3 | *.war 4 | *.zip 5 | *.tar 6 | *.tar.gz 7 | 8 | # eclipse ignore 9 | .settings/ 10 | .project 11 | .classpath 12 | 13 | # idea ignore 14 | .idea/ 15 | *.ipr 16 | *.iml 17 | *.iws 18 | 19 | # temp ignore 20 | *.log 21 | *.logs 22 | *.cache 23 | *.diff 24 | *.patch 25 | *.tmp 26 | *.java~ 27 | *.properties~ 28 | *.xml~ 29 | 30 | # system ignore 31 | .DS_Store 32 | Thumbs.db -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | 3 | Apache License 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | 180 | APPENDIX: How to apply the Apache License to your work. 181 | 182 | To apply the Apache License to your work, attach the following 183 | boilerplate notice, with the fields enclosed by brackets "[]" 184 | replaced with your own identifying information. (Don't include 185 | the brackets!) The text should be enclosed in the appropriate 186 | comment syntax for the file format. We also recommend that a 187 | file or class name and description of purpose be included on the 188 | same "printed page" as the copyright notice for easier 189 | identification within third-party archives. 190 | 191 | Copyright [yyyy] [name of copyright owner] 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. 204 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2018-yyyy 冰河. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | ================================================================ 16 | Dependencies: 17 | 18 | Spring: 19 | 20 | * LICENSE: 21 | * http://www.apache.org/licenses/LICENSE-2.0 (Apache License 2.0) 22 | * HOMEPAGE: 23 | * http://www.springsource.org 24 | 25 | Javassist: 26 | 27 | * LICENSE: 28 | * http://www.apache.org/licenses/LICENSE-2.0 (Apache License 2.0) 29 | * HOMEPAGE: 30 | * http://www.jboss.org/javassist 31 | 32 | Netty: 33 | 34 | * LICENSE: 35 | * http://www.apache.org/licenses/LICENSE-2.0 (Apache License 2.0) 36 | * HOMEPAGE: 37 | * http://netty.io 38 | 39 | Mina: 40 | 41 | * LICENSE: 42 | * http://www.apache.org/licenses/LICENSE-2.0 (Apache License 2.0) 43 | * HOMEPAGE: 44 | * http://mina.apache.org 45 | 46 | Grizzly: 47 | 48 | * LICENSE: 49 | * http://www.gnu.org/licenses/gpl-2.0.html (General Public License 2.0) 50 | * HOMEPAGE: 51 | * http://grizzly.java.net 52 | 53 | HttpClient: 54 | 55 | * LICENSE: 56 | * http://www.apache.org/licenses/LICENSE-2.0 (Apache License 2.0) 57 | * HOMEPAGE: 58 | * http://hc.apache.org 59 | 60 | Hessian: 61 | 62 | * LICENSE: 63 | * http://www.apache.org/licenses/LICENSE-2.0 (Apache License 2.0) 64 | * HOMEPAGE: 65 | * http://hessian.caucho.com 66 | 67 | XStream: 68 | 69 | * LICENSE: 70 | * http://www.apache.org/licenses/LICENSE-2.0 (Apache License 2.0) 71 | * HOMEPAGE: 72 | * http://xstream.codehaus.org 73 | 74 | FastJson: 75 | 76 | * LICENSE: 77 | * http://www.apache.org/licenses/LICENSE-2.0 (Apache License 2.0) 78 | * HOMEPAGE: 79 | * http://code.alibabatech.com/wiki/fastjson 80 | 81 | Zookeeper: 82 | 83 | * LICENSE: 84 | * http://www.apache.org/licenses/LICENSE-2.0 (Apache License 2.0) 85 | * HOMEPAGE: 86 | * http://zookeeper.apache.org 87 | 88 | Jedis: 89 | 90 | * LICENSE: 91 | * http://www.apache.org/licenses/LICENSE-2.0 (Apache License 2.0) 92 | * HOMEPAGE: 93 | * http://code.google.com/p/jedis 94 | 95 | XMemcached: 96 | 97 | * LICENSE: 98 | * http://www.apache.org/licenses/LICENSE-2.0 (Apache License 2.0) 99 | * HOMEPAGE: 100 | * http://code.google.com/p/xmemcached 101 | 102 | Jetty: 103 | 104 | * LICENSE: 105 | * http://www.apache.org/licenses/LICENSE-2.0 (Apache License 2.0) 106 | * HOMEPAGE: 107 | * http://jetty.mortbay.org 108 | 109 | Thrift: 110 | 111 | * LICENSE: 112 | * http://www.apache.org/licenses/LICENSE-2.0 (Apache License 2.0) 113 | * HOMEPAGE: 114 | * http://thrift.apache.org 115 | 116 | CXF: 117 | 118 | * LICENSE: 119 | * http://www.apache.org/licenses/LICENSE-2.0 (Apache License 2.0) 120 | * HOMEPAGE: 121 | * http://cxf.apache.org 122 | 123 | ZKClient: 124 | 125 | * LICENSE: 126 | * http://www.apache.org/licenses/LICENSE-2.0 (Apache License 2.0) 127 | * HOMEPAGE: 128 | * https://github.com/sgroschupf/zkclient 129 | 130 | Curator 131 | 132 | * LICENSE: 133 | * http://www.apache.org/licenses/LICENSE-2.0 (Apache License 2.0) 134 | * HOMEPAGE: 135 | * https://github.com/Netflix/curator 136 | 137 | JFreeChart: 138 | 139 | * LICENSE: 140 | * http://www.apache.org/licenses/LICENSE-2.0 (Apache License 2.0) 141 | * HOMEPAGE: 142 | * http://www.jfree.org 143 | 144 | HibernateValidator: 145 | 146 | * LICENSE: 147 | * http://www.apache.org/licenses/LICENSE-2.0 (Apache License 2.0) 148 | * HOMEPAGE: 149 | * http://www.hibernate.org/subprojects/validator.html 150 | 151 | CommonsLogging: 152 | 153 | * LICENSE: 154 | * http://www.apache.org/licenses/LICENSE-2.0 (Apache License 2.0) 155 | * HOMEPAGE: 156 | * http://commons.apache.org/logging 157 | 158 | SLF4J: 159 | 160 | * LICENSE: 161 | * http://www.apache.org/licenses/LICENSE-2.0 (Apache License 2.0) 162 | * HOMEPAGE: 163 | * http://www.slf4j.org 164 | 165 | Log4j: 166 | 167 | * LICENSE: 168 | * http://www.apache.org/licenses/LICENSE-2.0 (Apache License 2.0) 169 | * HOMEPAGE: 170 | * http://log4j.apache.org 171 | -------------------------------------------------------------------------------- /images/2023-11-14-001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe001/spring-redis/2d6e86d8c0ee545122f31bce3f250eb667816793/images/2023-11-14-001.png -------------------------------------------------------------------------------- /images/2023-11-14-005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe001/spring-redis/2d6e86d8c0ee545122f31bce3f250eb667816793/images/2023-11-14-005.png -------------------------------------------------------------------------------- /images/2023-11-14-017.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe001/spring-redis/2d6e86d8c0ee545122f31bce3f250eb667816793/images/2023-11-14-017.png -------------------------------------------------------------------------------- /images/2023-11-14-020.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe001/spring-redis/2d6e86d8c0ee545122f31bce3f250eb667816793/images/2023-11-14-020.png -------------------------------------------------------------------------------- /images/2023-11-14-021.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe001/spring-redis/2d6e86d8c0ee545122f31bce3f250eb667816793/images/2023-11-14-021.png -------------------------------------------------------------------------------- /images/2023-11-14-022.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe001/spring-redis/2d6e86d8c0ee545122f31bce3f250eb667816793/images/2023-11-14-022.png -------------------------------------------------------------------------------- /images/2023-11-14-023.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe001/spring-redis/2d6e86d8c0ee545122f31bce3f250eb667816793/images/2023-11-14-023.png -------------------------------------------------------------------------------- /images/2023-11-14-024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe001/spring-redis/2d6e86d8c0ee545122f31bce3f250eb667816793/images/2023-11-14-024.png -------------------------------------------------------------------------------- /images/2023-11-14-025.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe001/spring-redis/2d6e86d8c0ee545122f31bce3f250eb667816793/images/2023-11-14-025.png -------------------------------------------------------------------------------- /images/2023-11-14-026.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe001/spring-redis/2d6e86d8c0ee545122f31bce3f250eb667816793/images/2023-11-14-026.png -------------------------------------------------------------------------------- /images/bigdata.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe001/spring-redis/2d6e86d8c0ee545122f31bce3f250eb667816793/images/bigdata.png -------------------------------------------------------------------------------- /images/concurrent-001.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe001/spring-redis/2d6e86d8c0ee545122f31bce3f250eb667816793/images/concurrent-001.jpg -------------------------------------------------------------------------------- /images/concurrent-002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe001/spring-redis/2d6e86d8c0ee545122f31bce3f250eb667816793/images/concurrent-002.png -------------------------------------------------------------------------------- /images/concurrent-003.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe001/spring-redis/2d6e86d8c0ee545122f31bce3f250eb667816793/images/concurrent-003.jpg -------------------------------------------------------------------------------- /images/gongzhonghao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe001/spring-redis/2d6e86d8c0ee545122f31bce3f250eb667816793/images/gongzhonghao.png -------------------------------------------------------------------------------- /images/hacker_binghe.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe001/spring-redis/2d6e86d8c0ee545122f31bce3f250eb667816793/images/hacker_binghe.jpg -------------------------------------------------------------------------------- /images/ice_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe001/spring-redis/2d6e86d8c0ee545122f31bce3f250eb667816793/images/ice_video.png -------------------------------------------------------------------------------- /images/ice_wechat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe001/spring-redis/2d6e86d8c0ee545122f31bce3f250eb667816793/images/ice_wechat.jpg -------------------------------------------------------------------------------- /images/mysql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe001/spring-redis/2d6e86d8c0ee545122f31bce3f250eb667816793/images/mysql.png -------------------------------------------------------------------------------- /images/transaction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe001/spring-redis/2d6e86d8c0ee545122f31bce3f250eb667816793/images/transaction.png -------------------------------------------------------------------------------- /images/xingqiu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe001/spring-redis/2d6e86d8c0ee545122f31bce3f250eb667816793/images/xingqiu.png -------------------------------------------------------------------------------- /images/xingqiu_149.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binghe001/spring-redis/2d6e86d8c0ee545122f31bce3f250eb667816793/images/xingqiu_149.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | io.binghe.redis 8 | spring-redis 9 | 1.0.0-SNAPSHOT 10 | 11 | 12 | 8 13 | 8 14 | UTF-8 15 | 2.7.5 16 | 3.16.3 17 | 30.1-jre 18 | 5.4.0 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-parent 24 | 2.7.5 25 | 26 | 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-web 32 | 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-test 37 | 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-data-redis 42 | 43 | 44 | 45 | org.redisson 46 | redisson 47 | ${redisson.version} 48 | 49 | 50 | 51 | com.google.guava 52 | guava 53 | ${guava.version} 54 | 55 | 56 | 57 | cn.hutool 58 | hutool-all 59 | ${hutool.version} 60 | 61 | 62 | 63 | org.apache.commons 64 | commons-pool2 65 | 66 | 67 | 68 | junit 69 | junit 70 | test 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | org.springframework.boot 79 | spring-boot-dependencies 80 | ${spring-boot.version} 81 | pom 82 | import 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/main/java/io/binghe/redis/SpringRedisStarter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022-9999 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.binghe.redis; 17 | 18 | import io.binghe.redis.utils.ThreadPoolUtils; 19 | import org.springframework.boot.SpringApplication; 20 | import org.springframework.boot.autoconfigure.SpringBootApplication; 21 | 22 | /** 23 | * @author binghe(微信 : hacker_binghe) 24 | * @version 1.0.0 25 | * @description 启动类 26 | * @github https://github.com/binghe001 27 | * @copyright 公众号: 冰河技术 28 | */ 29 | @SpringBootApplication 30 | public class SpringRedisStarter { 31 | 32 | public static void main(String[] args) { 33 | Runtime.getRuntime().addShutdownHook(new Thread(() -> { 34 | ThreadPoolUtils.shutdown(); 35 | })); 36 | SpringApplication.run(SpringRedisStarter.class, args); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/io/binghe/redis/cache/distribute/DistributeCacheService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022-9999 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.binghe.redis.cache.distribute; 17 | 18 | import cn.hutool.core.convert.Convert; 19 | import cn.hutool.core.util.StrUtil; 20 | import cn.hutool.crypto.digest.MD5; 21 | import cn.hutool.json.JSONUtil; 22 | import io.binghe.redis.cache.distribute.conversion.TypeConversion; 23 | 24 | import java.util.Collection; 25 | import java.util.List; 26 | import java.util.Set; 27 | import java.util.concurrent.TimeUnit; 28 | import java.util.function.Function; 29 | import java.util.function.Supplier; 30 | 31 | /** 32 | * @author binghe(微信 : hacker_binghe) 33 | * @version 1.0.0 34 | * @description 分布式缓存接口,通用型接口,在满足分布式缓存的需求时,解决了缓存击穿、穿透和雪崩的问题 35 | * @github https://github.com/binghe001 36 | * @copyright 公众号: 冰河技术 37 | */ 38 | public interface DistributeCacheService { 39 | 40 | /** 41 | * 永久缓存 42 | * @param key 缓存key 43 | * @param value 缓存value 44 | */ 45 | void set(String key, Object value); 46 | 47 | /** 48 | * 将数据缓存一段时间 49 | * @param key 缓存key 50 | * @param value 缓存value 51 | * @param timeout 物理缓存的时长 52 | * @param unit 物理时间单位 53 | */ 54 | void set(String key, Object value, Long timeout, TimeUnit unit); 55 | 56 | /** 57 | * 设置缓存过期 58 | * @param key 缓存key 59 | * @param timeout 过期时长 60 | * @param unit 时间单位 61 | * @return 设置过期时间是否成功 62 | */ 63 | Boolean expire(String key, final long timeout, final TimeUnit unit); 64 | 65 | /** 66 | * 保存缓存时设置逻辑过期时间 67 | * @param key 缓存key 68 | * @param value 缓存value 69 | * @param timeout 缓存逻辑过期时长 70 | * @param unit 缓存逻辑时间单位 71 | */ 72 | void setWithLogicalExpire(String key, Object value, Long timeout, TimeUnit unit); 73 | 74 | /** 75 | * 获取缓存中的数据 76 | * @param key 缓存key 77 | * @return 缓存value 78 | */ 79 | String get(String key); 80 | 81 | /** 82 | * 获取缓存数据 83 | * @param key 缓存的key 84 | * @param targetClass 目标对象Class 85 | * @param 泛型 86 | * @return 返回的数据 87 | */ 88 | T getObject(String key, Class targetClass); 89 | 90 | /** 91 | * 根据key列表批量获取value 92 | * @param keys key列表 93 | * @return value集合 94 | */ 95 | List multiGet(Collection keys); 96 | 97 | /** 98 | * 根据正则表达式获取所有的key集合 99 | * @param pattern 正则表达式 100 | * @return key的集合 101 | */ 102 | Set keys(String pattern); 103 | 104 | /** 105 | * 删除指定的key 106 | * @param key key 107 | * @return 删除是否成功 108 | */ 109 | Boolean delete(String key); 110 | /** 111 | * 带参数查询对象和简单类型数据,防止缓存穿透 112 | * @param keyPrefix 缓存key的前缀 113 | * @param id 缓存的业务标识, 114 | * @param type 缓存的实际对象类型 115 | * @param dbFallback 查询数据库的Function函数 116 | * @param timeout 缓存的时长 117 | * @param unit 时间单位 118 | * @return 返回业务数据 119 | * @param 结果泛型 120 | * @param 查询数据库参数泛型,也是参数泛型类型 121 | */ 122 | R queryWithPassThrough(String keyPrefix, ID id, Class type, Function dbFallback, Long timeout, TimeUnit unit); 123 | 124 | /** 125 | * 不带参数查询对象和简单类型数据,防止缓存穿透 126 | * @param keyPrefix key的前缀 127 | * @param type 缓存的实际对象类型 128 | * @param dbFallback 无参数查询数据库数据 129 | * @param timeout 缓存的时长 130 | * @param unit 时间单位 131 | * @return 返回业务数据 132 | * @param 结果泛型 133 | */ 134 | R queryWithPassThroughWithoutArgs(String keyPrefix, Class type, Supplier dbFallback, Long timeout, TimeUnit unit); 135 | /** 136 | * 带参数查询集合数据,防止缓存穿透 137 | * @param keyPrefix 缓存key的前缀 138 | * @param id 缓存的业务标识, 139 | * @param type 缓存的实际对象类型 140 | * @param dbFallback 查询数据库的Function函数 141 | * @param timeout 缓存的时长 142 | * @param unit 时间单位 143 | * @return 返回业务数据 144 | * @param 结果泛型 145 | * @param 查询数据库参数泛型,也是参数泛型类型 146 | */ 147 | List queryWithPassThroughList(String keyPrefix, ID id, Class type, Function> dbFallback, Long timeout, TimeUnit unit); 148 | 149 | /** 150 | * 不带参数查询集合数据,防止缓存穿透 151 | * @param keyPrefix 缓存key的前缀 152 | * @param type 缓存的实际对象类型 153 | * @param dbFallback 无参数查询数据库数据 154 | * @param timeout 缓存的时长 155 | * @param unit 时间单位 156 | * @return 返回业务数据 157 | * @param 结果泛型 158 | */ 159 | List queryWithPassThroughListWithoutArgs(String keyPrefix, Class type, Supplier> dbFallback, Long timeout, TimeUnit unit); 160 | 161 | /** 162 | * 带参数查询数据,按照逻辑过期时间读取缓存数据,新开线程重建缓存,其他线程直接返回逻辑过期数据,不占用资源 163 | * @param keyPrefix 缓存key的前缀 164 | * @param id 缓存业务标识,也是查询数据库的参数 165 | * @param type 缓存的实际对象类型 166 | * @param dbFallback 查询数据库的Function函数 167 | * @param timeout 缓存逻辑过期时长 168 | * @param unit 缓存逻辑过期时间单位 169 | * @return 业务数据 170 | * @param 结果数据泛型类型 171 | * @param 查询数据库泛型类型,也是参数泛型类型 172 | */ 173 | R queryWithLogicalExpire(String keyPrefix, ID id, Class type, Function dbFallback, Long timeout, TimeUnit unit); 174 | 175 | /** 176 | * 不带参数查询数据,按照逻辑过期时间读取缓存数据,新开线程重建缓存,其他线程直接返回逻辑过期数据,不占用资源 177 | * @param keyPrefix 缓存key的前缀 178 | * @param type 缓存的实际对象类型 179 | * @param dbFallback 无参数查询数据库数据 180 | * @param timeout 缓存的时长 181 | * @param unit 时间单位 182 | * @return 返回业务数据 183 | * @param 结果泛型 184 | */ 185 | R queryWithLogicalExpireWithoutArgs(String keyPrefix, Class type, Supplier dbFallback, Long timeout, TimeUnit unit); 186 | /** 187 | * 带参数查询集合数据,按照逻辑过期时间读取缓存数据,新开线程重建缓存,其他线程直接返回逻辑过期数据,不占用资源 188 | * @param keyPrefix 缓存key的前缀 189 | * @param id 缓存业务标识,也是查询数据库的参数 190 | * @param type 缓存的实际对象类型 191 | * @param dbFallback 查询数据库的Function函数 192 | * @param timeout 缓存逻辑过期时长 193 | * @param unit 缓存逻辑过期时间单位 194 | * @return 业务数据 195 | * @param 结果数据泛型类型 196 | * @param 查询数据库泛型类型,也是参数泛型类型 197 | */ 198 | List queryWithLogicalExpireList(String keyPrefix, ID id, Class type, Function> dbFallback, Long timeout, TimeUnit unit); 199 | 200 | /** 201 | * 不带参数查询集合数据,按照逻辑过期时间读取缓存数据,新开线程重建缓存,其他线程直接返回逻辑过期数据,不占用资源 202 | * @param keyPrefix 缓存key的前缀 203 | * @param type 缓存的实际对象类型 204 | * @param dbFallback 无参数查询数据库数据 205 | * @param timeout 缓存的时长 206 | * @param unit 时间单位 207 | * @return 返回业务数据 208 | * @param 结果泛型 209 | */ 210 | List queryWithLogicalExpireListWithoutArgs(String keyPrefix, Class type, Supplier> dbFallback, Long timeout, TimeUnit unit); 211 | 212 | /** 213 | * 带参数查询数据,按照互斥锁方式获取缓存数据,同一时刻只有一个线程访问数据库,其他线程访问不到数据重试 214 | * @param keyPrefix 缓存key的前缀 215 | * @param id 缓存业务标识,也是查询数据库的参数 216 | * @param type 缓存的实际对象类型 217 | * @param dbFallback 查询数据库的Function函数 218 | * @param timeout 缓存时长 219 | * @param unit 时间单位 220 | * @return 业务数据 221 | * @param 结果数据泛型类型 222 | * @param 查询数据库泛型类型,也是参数泛型类型 223 | */ 224 | R queryWithMutex(String keyPrefix, ID id, Class type, Function dbFallback, Long timeout, TimeUnit unit); 225 | 226 | /** 227 | * 不带参数查询数据,按照互斥锁方式获取缓存数据,同一时刻只有一个线程访问数据库,其他线程访问不到数据重试 228 | * @param keyPrefix 缓存key的前缀 229 | * @param type 缓存的实际对象类型 230 | * @param dbFallback 无参数查询数据库数据 231 | * @param timeout 缓存时长 232 | * @param unit 时间单位 233 | * @return 返回业务数据 234 | * @param 结果泛型 235 | */ 236 | R queryWithMutexWithoutArgs(String keyPrefix, Class type, Supplier dbFallback, Long timeout, TimeUnit unit); 237 | /** 238 | * 带参数查询数据,按照互斥锁方式获取缓存数据,同一时刻只有一个线程访问数据库,其他线程访问不到数据重试 239 | * @param keyPrefix 缓存key的前缀 240 | * @param id 缓存业务标识,也是查询数据库的参数 241 | * @param type 缓存的实际对象类型 242 | * @param dbFallback 查询数据库的Function函数 243 | * @param timeout 缓存时长 244 | * @param unit 时间单位 245 | * @return 业务数据 246 | * @param 结果数据泛型类型 247 | * @param 查询数据库泛型类型,也是参数泛型类型 248 | */ 249 | List queryWithMutexList(String keyPrefix, ID id, Class type, Function> dbFallback, Long timeout, TimeUnit unit); 250 | 251 | /** 252 | * 不带参数查询数据,按照互斥锁方式获取缓存数据,同一时刻只有一个线程访问数据库,其他线程访问不到数据重试 253 | * @param keyPrefix 缓存key的前缀 254 | * @param type 缓存的实际对象类型 255 | * @param dbFallback 无参数查询数据库数据 256 | * @param timeout 缓存时长 257 | * @param unit 时间单位 258 | * @return 返回业务数据 259 | * @param 结果泛型 260 | */ 261 | List queryWithMutexListWithoutArgs(String keyPrefix, Class type, Supplier> dbFallback, Long timeout, TimeUnit unit); 262 | 263 | /** 264 | * 将对象类型的json字符串转换成泛型类型 265 | * @param obj 未知类型对象 266 | * @param type 泛型Class类型 267 | * @return 泛型对象 268 | * @param 泛型 269 | */ 270 | default R getResult(Object obj, Class type){ 271 | if (obj == null){ 272 | return null; 273 | } 274 | //简单类型 275 | if (TypeConversion.isSimpleType(obj)){ 276 | return Convert.convert(type, obj); 277 | } 278 | return JSONUtil.toBean(JSONUtil.toJsonStr(obj), type); 279 | } 280 | 281 | /** 282 | * 将对象类型的json字符串转换成泛型类型的List集合 283 | * @param str json字符串 284 | * @param type 泛型Class类型 285 | * @return 泛型List集合 286 | * @param 泛型 287 | */ 288 | default List getResultList(String str, Class type){ 289 | if (StrUtil.isEmpty(str)){ 290 | return null; 291 | } 292 | return JSONUtil.toList(JSONUtil.parseArray(str), type); 293 | } 294 | 295 | /** 296 | * 获取简单的key 297 | * @param key key 298 | * @return 返回key 299 | */ 300 | default String getKey(String key){ 301 | return getKey(key, null); 302 | } 303 | 304 | /** 305 | * 不确定参数类型的情况下,使用MD5计算参数的拼接到Redis中的唯一Key 306 | * @param keyPrefix 缓存key的前缀 307 | * @param id 泛型参数 308 | * @return 拼接好的缓存key 309 | * @param 参数泛型类型 310 | */ 311 | default String getKey(String keyPrefix, ID id){ 312 | if (id == null){ 313 | return keyPrefix; 314 | } 315 | String key = ""; 316 | //简单数据类型与简单字符串 317 | if (TypeConversion.isSimpleType(id)){ 318 | key = StrUtil.toString(id); 319 | }else { 320 | key = MD5.create().digestHex(JSONUtil.toJsonStr(id)); 321 | } 322 | if (StrUtil.isEmpty(key)){ 323 | key = ""; 324 | } 325 | return keyPrefix.concat(key); 326 | } 327 | 328 | /** 329 | * 获取要保存到缓存中的value字符串,可能是简单类型,也可能是对象类型,也可能是集合数组等 330 | * @param value 要保存的value值 331 | * @return 处理好的字符串 332 | */ 333 | default String getValue(Object value){ 334 | return TypeConversion.isSimpleType(value) ? String.valueOf(value) : JSONUtil.toJsonStr(value); 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /src/main/java/io/binghe/redis/cache/distribute/conversion/TypeConversion.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022-9999 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.binghe.redis.cache.distribute.conversion; 17 | 18 | import cn.hutool.core.convert.Convert; 19 | import cn.hutool.core.thread.lock.LockUtil; 20 | import cn.hutool.json.JSONUtil; 21 | 22 | import java.util.Collection; 23 | 24 | /** 25 | * @author binghe(微信 : hacker_binghe) 26 | * @version 1.0.0 27 | * @description 类型转换 28 | * @github https://github.com/binghe001 29 | * @copyright 公众号: 冰河技术 30 | */ 31 | public class TypeConversion { 32 | public static boolean isCollectionType(T t){ 33 | return t instanceof Collection; 34 | } 35 | 36 | /** 37 | * 简单字符串和基本类型统称为简单类型 38 | */ 39 | public static boolean isSimpleType(T t){ 40 | return isSimpleString(t) || isInt(t) || isLong(t) || isDouble(t) || isFloat(t) || isChar(t) || isBoolean(t) || isShort(t) || isByte(t); 41 | } 42 | 43 | public static boolean isSimpleString(T t){ 44 | if (t == null || !isString(t)){ 45 | return false; 46 | } 47 | return !JSONUtil.isJson(t.toString()); 48 | } 49 | 50 | public static boolean isString(T t) { 51 | return t instanceof String; 52 | } 53 | 54 | public static boolean isByte(T t) { 55 | return t instanceof Byte; 56 | } 57 | 58 | public static boolean isShort(T t) { 59 | return t instanceof Short; 60 | } 61 | 62 | public static boolean isInt(T t) { 63 | return t instanceof Integer; 64 | } 65 | 66 | public static boolean isLong(T t) { 67 | return t instanceof Long; 68 | } 69 | 70 | public static boolean isChar(T t) { 71 | return t instanceof Character; 72 | } 73 | 74 | public static boolean isFloat(T t) { 75 | return t instanceof Float; 76 | } 77 | 78 | public static boolean isDouble(T t) { 79 | return t instanceof Double; 80 | } 81 | 82 | public static boolean isBoolean(T t) { 83 | return t instanceof Boolean; 84 | } 85 | 86 | public static Class getClassType(T t) { 87 | return t.getClass(); 88 | } 89 | 90 | public static R convertor(String str, Class type){ 91 | return Convert.convert(type, str); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/io/binghe/redis/cache/distribute/data/RedisData.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022-9999 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.binghe.redis.cache.distribute.data; 17 | 18 | import java.time.LocalDateTime; 19 | 20 | /** 21 | * @author binghe(微信 : hacker_binghe) 22 | * @version 1.0.0 23 | * @description 缓存到Redis中的数据,主要配合使用数据的逻辑过期 24 | * @github https://github.com/binghe001 25 | * @copyright 公众号: 冰河技术 26 | */ 27 | public class RedisData { 28 | //实际业务数据 29 | private Object data; 30 | //过期时间点 31 | private LocalDateTime expireTime; 32 | 33 | public RedisData() { 34 | } 35 | 36 | public RedisData(Object data, LocalDateTime expireTime) { 37 | this.data = data; 38 | this.expireTime = expireTime; 39 | } 40 | 41 | public Object getData() { 42 | return data; 43 | } 44 | 45 | public void setData(Object data) { 46 | this.data = data; 47 | } 48 | 49 | public LocalDateTime getExpireTime() { 50 | return expireTime; 51 | } 52 | 53 | public void setExpireTime(LocalDateTime expireTime) { 54 | this.expireTime = expireTime; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/io/binghe/redis/cache/distribute/redis/RedisDistributeCacheService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022-9999 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.binghe.redis.cache.distribute.redis; 17 | 18 | import cn.hutool.core.util.StrUtil; 19 | import cn.hutool.json.JSONUtil; 20 | import io.binghe.redis.cache.distribute.DistributeCacheService; 21 | import io.binghe.redis.cache.distribute.data.RedisData; 22 | import io.binghe.redis.lock.DistributedLock; 23 | import io.binghe.redis.lock.factory.DistributedLockFactory; 24 | import io.binghe.redis.utils.ThreadPoolUtils; 25 | import org.slf4j.Logger; 26 | import org.slf4j.LoggerFactory; 27 | import org.springframework.beans.factory.annotation.Autowired; 28 | import org.springframework.beans.factory.annotation.Qualifier; 29 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 30 | import org.springframework.data.redis.core.StringRedisTemplate; 31 | import org.springframework.stereotype.Component; 32 | 33 | import java.time.LocalDateTime; 34 | import java.util.ArrayList; 35 | import java.util.Collection; 36 | import java.util.List; 37 | import java.util.Set; 38 | import java.util.concurrent.TimeUnit; 39 | import java.util.function.Function; 40 | import java.util.function.Supplier; 41 | 42 | /** 43 | * @author binghe(微信 : hacker_binghe) 44 | * @version 1.0.0 45 | * @description 基于Redis实现的分布式缓存,在满足分布式缓存的需求时,解决了缓存击穿、穿透和雪崩的问题 46 | * @github https://github.com/binghe001 47 | * @copyright 公众号: 冰河技术 48 | */ 49 | @Component 50 | @ConditionalOnProperty(name = "cache.type.distribute", havingValue = "redis") 51 | public class RedisDistributeCacheService implements DistributeCacheService { 52 | 53 | private final Logger logger = LoggerFactory.getLogger(RedisDistributeCacheService.class); 54 | 55 | //缓存空数据的时长,单位秒 56 | private static final Long CACHE_NULL_TTL = 60L; 57 | //缓存的空数据 58 | private static final String EMPTY_VALUE = ""; 59 | //缓存的空列表数据 60 | private static final String EMPTY_LIST_VALUE = "[]"; 61 | //分布式锁key的后缀 62 | private static final String LOCK_SUFFIX = "_lock"; 63 | //线程休眠的毫秒数 64 | private static final long THREAD_SLEEP_MILLISECONDS = 50; 65 | 66 | @Autowired 67 | @Qualifier("stringRedisTemplate") 68 | private StringRedisTemplate redisTemplate; 69 | 70 | @Autowired 71 | private DistributedLockFactory distributedLockFactory; 72 | 73 | @Override 74 | public void set(String key, Object value) { 75 | redisTemplate.opsForValue().set(key, this.getValue(value)); 76 | } 77 | 78 | @Override 79 | public void set(String key, Object value, Long timeout, TimeUnit unit) { 80 | redisTemplate.opsForValue().set(key, this.getValue(value), timeout, unit); 81 | } 82 | 83 | @Override 84 | public Boolean expire(String key, long timeout, TimeUnit unit) { 85 | return redisTemplate.expire(key, timeout, unit); 86 | } 87 | 88 | @Override 89 | public void setWithLogicalExpire(String key, Object value, Long timeout, TimeUnit unit) { 90 | RedisData redisData = new RedisData(value, LocalDateTime.now().plusSeconds(unit.toSeconds(timeout))); 91 | redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData)); 92 | } 93 | 94 | @Override 95 | public String get(String key) { 96 | return redisTemplate.opsForValue().get(key); 97 | } 98 | 99 | @Override 100 | public T getObject(String key, Class targetClass) { 101 | Object result = redisTemplate.opsForValue().get(key); 102 | if (result == null) { 103 | return null; 104 | } 105 | try { 106 | return JSONUtil.toBean(result.toString(), targetClass); 107 | } catch (Exception e) { 108 | return null; 109 | } 110 | } 111 | 112 | @Override 113 | public List multiGet(Collection keys) { 114 | return redisTemplate.opsForValue().multiGet(keys); 115 | } 116 | 117 | @Override 118 | public Set keys(String pattern) { 119 | return redisTemplate.keys(pattern); 120 | } 121 | 122 | @Override 123 | public Boolean delete(String key) { 124 | if (StrUtil.isEmpty(key)) { 125 | return false; 126 | } 127 | return redisTemplate.delete(key); 128 | } 129 | 130 | @Override 131 | public R queryWithPassThrough(String keyPrefix, ID id, Class type, Function dbFallback, Long timeout, TimeUnit unit) { 132 | //获取存储到Redis中的数据key 133 | String key = this.getKey(keyPrefix, id); 134 | //从Redis查询缓存数据 135 | String str = redisTemplate.opsForValue().get(key); 136 | //缓存存在数据,直接返回 137 | if (StrUtil.isNotBlank(str)){ 138 | //返回数据 139 | return this.getResult(str, type); 140 | } 141 | //缓存中存储的是空字符串 142 | if (str != null){ 143 | //直接返回空 144 | return null; 145 | } 146 | //从数据库查询数据 147 | R r = dbFallback.apply(id); 148 | //数据数据为空 149 | if (r == null){ 150 | redisTemplate.opsForValue().set(key, EMPTY_VALUE, CACHE_NULL_TTL, TimeUnit.SECONDS); 151 | return null; 152 | } 153 | //缓存数据 154 | this.set(key, r, timeout, unit); 155 | return r; 156 | } 157 | 158 | @Override 159 | public R queryWithPassThroughWithoutArgs(String keyPrefix, Class type, Supplier dbFallback, Long timeout, TimeUnit unit) { 160 | //获取存储到Redis中的数据key 161 | String key = this.getKey(keyPrefix); 162 | //从Redis查询缓存数据 163 | String str = redisTemplate.opsForValue().get(key); 164 | //缓存存在数据,直接返回 165 | if (StrUtil.isNotBlank(str)){ 166 | //返回数据 167 | return this.getResult(str, type); 168 | } 169 | //缓存中存储的是空字符串 170 | if (str != null){ 171 | //直接返回空 172 | return null; 173 | } 174 | //从数据库查询数据 175 | R r = dbFallback.get(); 176 | //数据数据为空 177 | if (r == null){ 178 | redisTemplate.opsForValue().set(key, EMPTY_VALUE, CACHE_NULL_TTL, TimeUnit.SECONDS); 179 | return null; 180 | } 181 | //缓存数据 182 | this.set(key, r, timeout, unit); 183 | return r; 184 | } 185 | 186 | @Override 187 | public List queryWithPassThroughList(String keyPrefix, ID id, Class type, Function> dbFallback, Long timeout, TimeUnit unit) { 188 | //获取存储到Redis中的数据key 189 | String key = this.getKey(keyPrefix, id); 190 | //从Redis查询缓存数据 191 | String str = redisTemplate.opsForValue().get(key); 192 | //缓存存在数据,直接返回 193 | if (StrUtil.isNotBlank(str)){ 194 | //返回数据 195 | return this.getResultList(str, type); 196 | } 197 | if (str != null){ 198 | //直接返回数据 199 | return null; 200 | } 201 | List r = dbFallback.apply(id); 202 | //数据库数据为空 203 | if (r == null || r.isEmpty()){ 204 | redisTemplate.opsForValue().set(key, EMPTY_VALUE, CACHE_NULL_TTL, TimeUnit.SECONDS); 205 | return null; 206 | } 207 | this.set(key, r, timeout, unit); 208 | return r; 209 | } 210 | 211 | @Override 212 | public List queryWithPassThroughListWithoutArgs(String keyPrefix, Class type, Supplier> dbFallback, Long timeout, TimeUnit unit) { 213 | //获取存储到Redis中的数据key 214 | String key = this.getKey(keyPrefix); 215 | //从Redis查询缓存数据 216 | String str = redisTemplate.opsForValue().get(key); 217 | //缓存存在数据,直接返回 218 | if (StrUtil.isNotBlank(str)){ 219 | //返回数据 220 | return this.getResultList(str, type); 221 | } 222 | if (str != null){ 223 | //直接返回数据 224 | return null; 225 | } 226 | List r = dbFallback.get(); 227 | //数据库数据为空 228 | if (r == null || r.isEmpty()){ 229 | redisTemplate.opsForValue().set(key, EMPTY_VALUE, CACHE_NULL_TTL, TimeUnit.SECONDS); 230 | return null; 231 | } 232 | this.set(key, r, timeout, unit); 233 | return r; 234 | } 235 | 236 | @Override 237 | public R queryWithLogicalExpire(String keyPrefix, ID id, Class type, Function dbFallback, Long timeout, TimeUnit unit) { 238 | //获取存储到Redis中的数据key 239 | String key = this.getKey(keyPrefix, id); 240 | //从Redis获取缓存数据 241 | String str = redisTemplate.opsForValue().get(key); 242 | //判断数据是否存在 243 | if (StrUtil.isBlank(str)){ 244 | try{ 245 | // 构建缓存数据 246 | buildCache(id, dbFallback, timeout, unit, key); 247 | Thread.sleep(THREAD_SLEEP_MILLISECONDS); 248 | //重试 249 | return queryWithLogicalExpire(keyPrefix, id, type, dbFallback, timeout, unit); 250 | }catch (InterruptedException e){ 251 | logger.error("query data with logical expire|{}", e.getMessage()); 252 | throw new RuntimeException(e); 253 | } 254 | } 255 | //命中,需要先把json反序列化为对象 256 | RedisData redisData = this.getResult(str, RedisData.class); 257 | if (EMPTY_VALUE.equals(redisData.getData())){ 258 | return null; 259 | } 260 | R r = this.getResult(redisData.getData(), type); 261 | LocalDateTime expireTime = redisData.getExpireTime(); 262 | //判断是否过期 263 | if (expireTime.isAfter(LocalDateTime.now())){ 264 | // 未过期,直接返回数据 265 | return r; 266 | } 267 | //缓存获取,构建缓存数据 268 | buildCache(id, dbFallback, timeout, unit, key); 269 | //返回逻辑过期数据 270 | return r; 271 | } 272 | 273 | /** 274 | * 构建缓存逻辑过期数据 275 | */ 276 | private void buildCache(ID id, Function dbFallback, Long timeout, TimeUnit unit, String key) { 277 | // 分布式锁 278 | String lockKey = this.getLockKey(key); 279 | //获取分布式锁 280 | DistributedLock distributedLock = distributedLockFactory.getDistributedLock(lockKey); 281 | ThreadPoolUtils.execute(() -> { 282 | try{ 283 | boolean isLock = distributedLock.tryLock(); 284 | //获取锁成功, Double Check 285 | if (isLock){ 286 | R newR = null; 287 | //从Redis获取缓存数据 288 | String str = redisTemplate.opsForValue().get(key); 289 | if (StrUtil.isEmpty(str)){ 290 | //查询数据库 291 | newR = dbFallback.apply(id); 292 | }else{ 293 | //命中,需要先把json反序列化为对象 294 | RedisData redisData = this.getResult(str, RedisData.class); 295 | LocalDateTime expireTime = redisData.getExpireTime(); 296 | if (expireTime.isBefore(LocalDateTime.now())){ 297 | //查询数据库 298 | newR = dbFallback.apply(id); 299 | }else{ 300 | return; 301 | } 302 | } 303 | if (newR != null){ 304 | // 重建缓存 305 | this.setWithLogicalExpire(key, newR, timeout, unit); 306 | }else{ 307 | this.setWithLogicalExpire(key, EMPTY_VALUE, CACHE_NULL_TTL, TimeUnit.SECONDS); 308 | } 309 | } 310 | }catch (InterruptedException e){ 311 | logger.error("build cache | {}", e.getMessage()); 312 | throw new RuntimeException(e); 313 | }finally { 314 | distributedLock.unlock(); 315 | } 316 | }); 317 | } 318 | 319 | @Override 320 | public R queryWithLogicalExpireWithoutArgs(String keyPrefix, Class type, Supplier dbFallback, Long timeout, TimeUnit unit) { 321 | //获取存储到Redis中的数据key 322 | String key = this.getKey(keyPrefix); 323 | //从Redis获取缓存数据 324 | String str = redisTemplate.opsForValue().get(key); 325 | //判断数据是否存在 326 | if (StrUtil.isBlank(str)){ 327 | try{ 328 | // 构建缓存数据 329 | buildCacheWithoutArgs(dbFallback, timeout, unit, key); 330 | Thread.sleep(THREAD_SLEEP_MILLISECONDS); 331 | //重试 332 | return queryWithLogicalExpireWithoutArgs(keyPrefix, type, dbFallback, timeout, unit); 333 | }catch (InterruptedException e){ 334 | logger.error("query data with logical expire|{}", e.getMessage()); 335 | throw new RuntimeException(e); 336 | } 337 | } 338 | //命中,需要先把json反序列化为对象 339 | RedisData redisData = this.getResult(str, RedisData.class); 340 | if (EMPTY_VALUE.equals(redisData.getData())){ 341 | return null; 342 | } 343 | R r = this.getResult(redisData.getData(), type); 344 | LocalDateTime expireTime = redisData.getExpireTime(); 345 | //判断是否过期 346 | if (expireTime.isAfter(LocalDateTime.now())){ 347 | // 未过期,直接返回数据 348 | return r; 349 | } 350 | //缓存获取,构建缓存数据 351 | buildCacheWithoutArgs(dbFallback, timeout, unit, key); 352 | //返回逻辑过期数据 353 | return r; 354 | } 355 | 356 | /** 357 | * 构建缓存逻辑过期数据 358 | */ 359 | private void buildCacheWithoutArgs(Supplier dbFallback, Long timeout, TimeUnit unit, String key) { 360 | // 分布式锁 361 | String lockKey = this.getLockKey(key); 362 | //获取分布式锁 363 | DistributedLock distributedLock = distributedLockFactory.getDistributedLock(lockKey); 364 | ThreadPoolUtils.execute(() -> { 365 | try{ 366 | boolean isLock = distributedLock.tryLock(); 367 | //获取锁成功, Double Check 368 | if (isLock){ 369 | R newR = null; 370 | //从Redis获取缓存数据 371 | String str = redisTemplate.opsForValue().get(key); 372 | if (StrUtil.isEmpty(str)){ 373 | //查询数据库 374 | newR = dbFallback.get(); 375 | }else{ 376 | //命中,需要先把json反序列化为对象 377 | RedisData redisData = this.getResult(str, RedisData.class); 378 | LocalDateTime expireTime = redisData.getExpireTime(); 379 | if (expireTime.isBefore(LocalDateTime.now())){ 380 | //查询数据库 381 | newR = dbFallback.get(); 382 | }else { 383 | return; 384 | } 385 | } 386 | if (newR != null){ 387 | // 重建缓存 388 | this.setWithLogicalExpire(key, newR, timeout, unit); 389 | }else { 390 | this.setWithLogicalExpire(key, EMPTY_VALUE, CACHE_NULL_TTL, TimeUnit.SECONDS); 391 | } 392 | } 393 | }catch (InterruptedException e){ 394 | logger.error("build cache | {}", e.getMessage()); 395 | throw new RuntimeException(e); 396 | }finally { 397 | distributedLock.unlock(); 398 | } 399 | }); 400 | } 401 | 402 | 403 | @Override 404 | public List queryWithLogicalExpireList(String keyPrefix, ID id, Class type, Function> dbFallback, Long timeout, TimeUnit unit) { 405 | //获取存储到Redis中的数据key 406 | String key = this.getKey(keyPrefix, id); 407 | //从Redis获取缓存数据 408 | String str = redisTemplate.opsForValue().get(key); 409 | //判断数据是否存在 410 | if (StrUtil.isBlank(str)){ 411 | try{ 412 | // 构建缓存数据 413 | buildCacheList(id, dbFallback, timeout, unit, key); 414 | Thread.sleep(THREAD_SLEEP_MILLISECONDS); 415 | //重试 416 | return queryWithLogicalExpireList(keyPrefix, id, type, dbFallback, timeout, unit); 417 | }catch (InterruptedException e){ 418 | logger.error("query data with logical expire|{}", e.getMessage()); 419 | throw new RuntimeException(e); 420 | } 421 | } 422 | //命中,需要先把json反序列化为对象 423 | RedisData redisData = this.getResult(str, RedisData.class); 424 | if (EMPTY_LIST_VALUE.equals(redisData.getData())){ 425 | return new ArrayList<>(); 426 | } 427 | List list = this.getResultList(JSONUtil.toJsonStr(redisData.getData()), type); 428 | LocalDateTime expireTime = redisData.getExpireTime(); 429 | //判断是否过期 430 | if (expireTime.isAfter(LocalDateTime.now())){ 431 | // 未过期,直接返回数据 432 | return list; 433 | } 434 | //缓存获取,构建缓存数据 435 | buildCacheList(id, dbFallback, timeout, unit, key); 436 | //返回逻辑过期数据 437 | return list; 438 | } 439 | 440 | /** 441 | * 构建缓存逻辑过期数据 442 | */ 443 | private void buildCacheList(ID id, Function> dbFallback, Long timeout, TimeUnit unit, String key) { 444 | // 分布式锁 445 | String lockKey = this.getLockKey(key); 446 | //获取分布式锁 447 | DistributedLock distributedLock = distributedLockFactory.getDistributedLock(lockKey); 448 | ThreadPoolUtils.execute(() -> { 449 | try{ 450 | boolean isLock = distributedLock.tryLock(); 451 | //获取锁成功, Double Check 452 | if (isLock){ 453 | List newR = null; 454 | //从Redis获取缓存数据 455 | String str = redisTemplate.opsForValue().get(key); 456 | if (StrUtil.isEmpty(str)){ 457 | //查询数据库 458 | newR = dbFallback.apply(id); 459 | }else{ 460 | //命中,需要先把json反序列化为对象 461 | RedisData redisData = this.getResult(str, RedisData.class); 462 | LocalDateTime expireTime = redisData.getExpireTime(); 463 | //缓存已经逻辑过期 464 | if (expireTime.isBefore(LocalDateTime.now())){ 465 | //查询数据库 466 | newR = dbFallback.apply(id); 467 | }else { 468 | return; 469 | } 470 | } 471 | if (newR != null){ 472 | // 重建缓存 473 | this.setWithLogicalExpire(key, newR, timeout, unit); 474 | }else { 475 | this.setWithLogicalExpire(key, EMPTY_LIST_VALUE, CACHE_NULL_TTL, TimeUnit.SECONDS); 476 | } 477 | } 478 | }catch (InterruptedException e){ 479 | logger.error("build cache list | {}", e.getMessage()); 480 | throw new RuntimeException(e); 481 | }finally { 482 | distributedLock.unlock(); 483 | } 484 | }); 485 | } 486 | 487 | @Override 488 | public List queryWithLogicalExpireListWithoutArgs(String keyPrefix, Class type, Supplier> dbFallback, Long timeout, TimeUnit unit) { 489 | //获取存储到Redis中的数据key 490 | String key = this.getKey(keyPrefix); 491 | //从Redis获取缓存数据 492 | String str = redisTemplate.opsForValue().get(key); 493 | //判断数据是否存在 494 | if (StrUtil.isBlank(str)){ 495 | try{ 496 | // 构建缓存数据 497 | buildCacheListWithoutArgs(dbFallback, timeout, unit, key); 498 | Thread.sleep(THREAD_SLEEP_MILLISECONDS); 499 | //重试 500 | return queryWithLogicalExpireListWithoutArgs(keyPrefix, type, dbFallback, timeout, unit); 501 | }catch (InterruptedException e){ 502 | logger.error("query data with logical expire|{}", e.getMessage()); 503 | throw new RuntimeException(e); 504 | } 505 | } 506 | //命中,需要先把json反序列化为对象 507 | RedisData redisData = this.getResult(str, RedisData.class); 508 | if (EMPTY_LIST_VALUE.equals(redisData.getData())){ 509 | return new ArrayList<>(); 510 | } 511 | List list = this.getResultList(JSONUtil.toJsonStr(redisData.getData()), type); 512 | LocalDateTime expireTime = redisData.getExpireTime(); 513 | //判断是否过期 514 | if (expireTime.isAfter(LocalDateTime.now())){ 515 | // 未过期,直接返回数据 516 | return list; 517 | } 518 | //缓存获取,构建缓存数据 519 | buildCacheListWithoutArgs(dbFallback, timeout, unit, key); 520 | //返回逻辑过期数据 521 | return list; 522 | } 523 | 524 | /** 525 | * 构建缓存逻辑过期数据 526 | */ 527 | private void buildCacheListWithoutArgs(Supplier> dbFallback, Long timeout, TimeUnit unit, String key) { 528 | // 分布式锁 529 | String lockKey = this.getLockKey(key); 530 | //获取分布式锁 531 | DistributedLock distributedLock = distributedLockFactory.getDistributedLock(lockKey); 532 | ThreadPoolUtils.execute(() -> { 533 | try{ 534 | boolean isLock = distributedLock.tryLock(); 535 | //获取锁成功, Double Check 536 | if (isLock){ 537 | List newR = null; 538 | //从Redis获取缓存数据 539 | String str = redisTemplate.opsForValue().get(key); 540 | if (StrUtil.isEmpty(str)){ 541 | //查询数据库 542 | newR = dbFallback.get(); 543 | }else{ 544 | //命中,需要先把json反序列化为对象 545 | RedisData redisData = this.getResult(str, RedisData.class); 546 | LocalDateTime expireTime = redisData.getExpireTime(); 547 | //缓存已经逻辑过期 548 | if (expireTime.isBefore(LocalDateTime.now())){ 549 | //查询数据库 550 | newR = dbFallback.get(); 551 | }else { 552 | return; 553 | } 554 | } 555 | if (newR != null){ 556 | // 重建缓存 557 | this.setWithLogicalExpire(key, newR, timeout, unit); 558 | }else { 559 | this.setWithLogicalExpire(key, EMPTY_LIST_VALUE, CACHE_NULL_TTL, TimeUnit.SECONDS); 560 | } 561 | } 562 | }catch (InterruptedException e){ 563 | logger.error("build cache list | {}", e.getMessage()); 564 | throw new RuntimeException(e); 565 | }finally { 566 | distributedLock.unlock(); 567 | } 568 | }); 569 | } 570 | 571 | 572 | @Override 573 | public R queryWithMutex(String keyPrefix, ID id, Class type, Function dbFallback, Long timeout, TimeUnit unit) { 574 | //获取存储到Redis中的数据key 575 | String key = this.getKey(keyPrefix, id); 576 | //从Redis获取缓存数据 577 | String str = redisTemplate.opsForValue().get(key); 578 | if (StrUtil.isNotBlank(str)){ 579 | //存在数据,直接返回 580 | return this.getResult(str, type); 581 | } 582 | //缓存了空字符串 583 | if (str != null){ 584 | return null; 585 | } 586 | String lockKey = this.getLockKey(key); 587 | R r = null; 588 | //获取分布式锁 589 | DistributedLock distributedLock = distributedLockFactory.getDistributedLock(lockKey); 590 | try { 591 | boolean isLock = distributedLock.tryLock(); 592 | //获取分布式锁失败,重试 593 | if (!isLock){ 594 | Thread.sleep(THREAD_SLEEP_MILLISECONDS); 595 | return queryWithMutex(keyPrefix, id, type, dbFallback, timeout, unit); 596 | } 597 | //获取锁成功, Double Check 598 | str = redisTemplate.opsForValue().get(key); 599 | if (StrUtil.isNotBlank(str)){ 600 | //存在数据,直接返回 601 | return this.getResult(str, type); 602 | } 603 | //成功获取到锁 604 | r = dbFallback.apply(id); 605 | //数据库本身不存在数据 606 | if (r == null){ 607 | //缓存空数据 608 | this.set(key, EMPTY_VALUE, CACHE_NULL_TTL, TimeUnit.SECONDS); 609 | return null; 610 | } 611 | //数据库存在数据 612 | this.set(key, r, timeout, unit); 613 | } catch (InterruptedException e) { 614 | logger.error("query data with mutex |{}", e.getMessage()); 615 | throw new RuntimeException(e); 616 | }finally { 617 | distributedLock.unlock(); 618 | } 619 | return r; 620 | } 621 | 622 | @Override 623 | public R queryWithMutexWithoutArgs(String keyPrefix, Class type, Supplier dbFallback, Long timeout, TimeUnit unit) { 624 | //获取存储到Redis中的数据key 625 | String key = this.getKey(keyPrefix); 626 | //从Redis获取缓存数据 627 | String str = redisTemplate.opsForValue().get(key); 628 | if (StrUtil.isNotBlank(str)){ 629 | //存在数据,直接返回 630 | return this.getResult(str, type); 631 | } 632 | //缓存了空字符串 633 | if (str != null){ 634 | return null; 635 | } 636 | String lockKey = this.getLockKey(key); 637 | R r = null; 638 | //获取分布式锁 639 | DistributedLock distributedLock = distributedLockFactory.getDistributedLock(lockKey); 640 | try { 641 | boolean isLock = distributedLock.tryLock(); 642 | //获取分布式锁失败,重试 643 | if (!isLock){ 644 | Thread.sleep(THREAD_SLEEP_MILLISECONDS); 645 | return queryWithMutexWithoutArgs(keyPrefix, type, dbFallback, timeout, unit); 646 | } 647 | //获取锁成功, Double Check 648 | str = redisTemplate.opsForValue().get(key); 649 | if (StrUtil.isNotBlank(str)){ 650 | //存在数据,直接返回 651 | return this.getResult(str, type); 652 | } 653 | r = dbFallback.get(); 654 | //数据库本身不存在数据 655 | if (r == null){ 656 | //缓存空数据 657 | this.set(key, EMPTY_VALUE, CACHE_NULL_TTL, TimeUnit.SECONDS); 658 | return null; 659 | } 660 | //数据库存在数据 661 | this.set(key, r, timeout, unit); 662 | } catch (InterruptedException e) { 663 | logger.error("query data with mutex |{}", e.getMessage()); 664 | throw new RuntimeException(e); 665 | }finally { 666 | distributedLock.unlock(); 667 | } 668 | return r; 669 | } 670 | 671 | @Override 672 | public List queryWithMutexList(String keyPrefix, ID id, Class type, Function> dbFallback, Long timeout, TimeUnit unit) { 673 | //获取存储到Redis中的数据key 674 | String key = this.getKey(keyPrefix, id); 675 | //从Redis获取缓存数据 676 | String str = redisTemplate.opsForValue().get(key); 677 | if (StrUtil.isNotBlank(str)){ 678 | //存在数据,直接返回 679 | return this.getResultList(str, type); 680 | } 681 | //缓存了空字符串 682 | if (str != null){ 683 | return null; 684 | } 685 | String lockKey = this.getLockKey(key); 686 | List list = null; 687 | // 获取分布式锁 688 | DistributedLock distributedLock = distributedLockFactory.getDistributedLock(lockKey); 689 | try { 690 | boolean isLock = distributedLock.tryLock(); 691 | //获取分布式锁失败,重试 692 | if (!isLock){ 693 | Thread.sleep(THREAD_SLEEP_MILLISECONDS); 694 | //重试 695 | return queryWithMutexList(keyPrefix, id, type, dbFallback, timeout, unit); 696 | } 697 | //获取锁成功, Double Check 698 | str = redisTemplate.opsForValue().get(key); 699 | if (StrUtil.isNotBlank(str)){ 700 | //存在数据,直接返回 701 | return this.getResultList(str, type); 702 | } 703 | list = dbFallback.apply(id); 704 | //数据库本身不存在数据 705 | if (list == null){ 706 | //缓存空数据 707 | redisTemplate.opsForValue().set(key, EMPTY_VALUE, CACHE_NULL_TTL, TimeUnit.SECONDS); 708 | return null; 709 | } 710 | //数据库存在数据 711 | this.set(key, list, timeout, unit); 712 | 713 | } catch (InterruptedException e) { 714 | logger.error("query data with mutex list |{}", e.getMessage()); 715 | throw new RuntimeException(e); 716 | }finally { 717 | distributedLock.unlock(); 718 | } 719 | return list; 720 | } 721 | 722 | @Override 723 | public List queryWithMutexListWithoutArgs(String keyPrefix, Class type, Supplier> dbFallback, Long timeout, TimeUnit unit) { 724 | //获取存储到Redis中的数据key 725 | String key = this.getKey(keyPrefix); 726 | //从Redis获取缓存数据 727 | String str = redisTemplate.opsForValue().get(key); 728 | if (StrUtil.isNotBlank(str)){ 729 | //存在数据,直接返回 730 | return this.getResultList(str, type); 731 | } 732 | //缓存了空字符串 733 | if (str != null){ 734 | return null; 735 | } 736 | String lockKey = this.getLockKey(key); 737 | List list = null; 738 | // 获取分布式锁 739 | DistributedLock distributedLock = distributedLockFactory.getDistributedLock(lockKey); 740 | try { 741 | boolean isLock = distributedLock.tryLock(); 742 | //获取分布式锁失败,重试 743 | if (!isLock){ 744 | Thread.sleep(THREAD_SLEEP_MILLISECONDS); 745 | //重试 746 | return queryWithMutexListWithoutArgs(keyPrefix, type, dbFallback, timeout, unit); 747 | } 748 | //获取锁成功, Double Check 749 | str = redisTemplate.opsForValue().get(key); 750 | if (StrUtil.isNotBlank(str)){ 751 | //存在数据,直接返回 752 | return this.getResultList(str, type); 753 | } 754 | list = dbFallback.get(); 755 | //数据库本身不存在数据 756 | if (list == null){ 757 | //缓存空数据 758 | redisTemplate.opsForValue().set(key, EMPTY_VALUE, CACHE_NULL_TTL, TimeUnit.SECONDS); 759 | return null; 760 | } 761 | //数据库存在数据 762 | this.set(key, list, timeout, unit); 763 | 764 | } catch (InterruptedException e) { 765 | logger.error("query data with mutex list |{}", e.getMessage()); 766 | throw new RuntimeException(e); 767 | }finally { 768 | distributedLock.unlock(); 769 | } 770 | return list; 771 | } 772 | 773 | //分布式锁Key 774 | private String getLockKey(String key){ 775 | return key.concat(LOCK_SUFFIX); 776 | } 777 | } 778 | -------------------------------------------------------------------------------- /src/main/java/io/binghe/redis/cache/local/LocalCacheService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022-9999 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.binghe.redis.cache.local; 17 | 18 | /** 19 | * @author binghe(微信 : hacker_binghe) 20 | * @version 1.0.0 21 | * @description 本地缓存接口 22 | * @github https://github.com/binghe001 23 | * @copyright 公众号: 冰河技术 24 | */ 25 | public interface LocalCacheService { 26 | /** 27 | * 向缓存中添加数据 28 | * @param key 缓存的key 29 | * @param value 缓存的value 30 | */ 31 | void put(K key, V value); 32 | 33 | /** 34 | * 根据key从缓存中查询数据 35 | * @param key 缓存的key 36 | * @return 缓存的value值 37 | */ 38 | V getIfPresent(Object key); 39 | 40 | /** 41 | * 移除缓存中的数据 42 | * @param key 缓存的key 43 | */ 44 | void remove(K key); 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/io/binghe/redis/cache/local/guava/factoty/LocalGuavaCacheFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022-9999 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.binghe.redis.cache.local.guava.factoty; 17 | 18 | import com.google.common.cache.Cache; 19 | import com.google.common.cache.CacheBuilder; 20 | 21 | import java.util.concurrent.TimeUnit; 22 | 23 | /** 24 | * @author binghe(微信 : hacker_binghe) 25 | * @version 1.0.0 26 | * @description 基于Guava的本地缓存工厂类 27 | * @github https://github.com/binghe001 28 | * @copyright 公众号: 冰河技术 29 | */ 30 | public class LocalGuavaCacheFactory { 31 | 32 | public static Cache getLocalCache(){ 33 | return CacheBuilder.newBuilder().initialCapacity(200).concurrencyLevel(5).expireAfterWrite(300, TimeUnit.SECONDS).build(); 34 | } 35 | 36 | public static Cache getLocalCache(long duration){ 37 | return CacheBuilder.newBuilder().initialCapacity(200).concurrencyLevel(5).expireAfterWrite(duration, TimeUnit.SECONDS).build(); 38 | } 39 | 40 | public static Cache getLocalCache(int initialCapacity, long duration){ 41 | return CacheBuilder.newBuilder().initialCapacity(initialCapacity).concurrencyLevel(5).expireAfterWrite(duration, TimeUnit.SECONDS).build(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/io/binghe/redis/cache/local/guava/impl/GuavaLocalCacheService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022-9999 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.binghe.redis.cache.local.guava.impl; 17 | 18 | import com.google.common.cache.Cache; 19 | import io.binghe.redis.cache.local.LocalCacheService; 20 | import io.binghe.redis.cache.local.guava.factoty.LocalGuavaCacheFactory; 21 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 22 | import org.springframework.stereotype.Component; 23 | import org.springframework.stereotype.Service; 24 | 25 | /** 26 | * @author binghe(微信 : hacker_binghe) 27 | * @version 1.0.0 28 | * @description 基于Guava实现的本地缓存 29 | * @github https://github.com/binghe001 30 | * @copyright 公众号: 冰河技术 31 | */ 32 | @Component 33 | @ConditionalOnProperty(name = "cache.type.local", havingValue = "guava") 34 | public class GuavaLocalCacheService implements LocalCacheService { 35 | //本地缓存,基于Guava实现 36 | private final Cache cache = LocalGuavaCacheFactory.getLocalCache(); 37 | 38 | @Override 39 | public void put(K key, V value) { 40 | cache.put(key, value); 41 | } 42 | 43 | @Override 44 | public V getIfPresent(Object key) { 45 | return cache.getIfPresent(key); 46 | } 47 | 48 | @Override 49 | public void remove(K key) { 50 | cache.invalidate(key); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/io/binghe/redis/config/RedisPoolConfig.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022-9999 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.binghe.redis.config; 17 | 18 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 19 | import com.fasterxml.jackson.annotation.JsonTypeInfo; 20 | import com.fasterxml.jackson.annotation.PropertyAccessor; 21 | import com.fasterxml.jackson.databind.ObjectMapper; 22 | import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator; 23 | import com.fasterxml.jackson.databind.module.SimpleModule; 24 | import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; 25 | import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; 26 | import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; 27 | import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; 28 | import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; 29 | import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; 30 | import io.lettuce.core.resource.ClientResources; 31 | import io.lettuce.core.resource.DefaultClientResources; 32 | import org.apache.commons.pool2.impl.GenericObjectPoolConfig; 33 | import org.springframework.beans.factory.annotation.Value; 34 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 35 | import org.springframework.context.annotation.Bean; 36 | import org.springframework.context.annotation.Configuration; 37 | import org.springframework.data.redis.connection.RedisStandaloneConfiguration; 38 | import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration; 39 | import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; 40 | import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration; 41 | import org.springframework.data.redis.core.RedisTemplate; 42 | import org.springframework.data.redis.core.StringRedisTemplate; 43 | import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; 44 | import org.springframework.data.redis.serializer.RedisSerializer; 45 | 46 | import java.time.Duration; 47 | import java.time.LocalDate; 48 | import java.time.LocalDateTime; 49 | import java.time.LocalTime; 50 | import java.time.format.DateTimeFormatter; 51 | 52 | /** 53 | * @author binghe(微信 : hacker_binghe) 54 | * @version 1.0.0 55 | * @description Redis连接池配置 56 | * @github https://github.com/binghe001 57 | * @copyright 公众号: 冰河技术 58 | */ 59 | @Configuration 60 | public class RedisPoolConfig { 61 | 62 | private static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; 63 | private static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd"; 64 | private static final String DEFAULT_TIME_FORMAT = "HH:mm:ss"; 65 | 66 | @Value("${spring.redis.lettuce.pool.max-idle}") 67 | private int maxIdle; 68 | @Value("${spring.redis.lettuce.pool.min-idle}") 69 | private int minIdle; 70 | @Value("${spring.redis.lettuce.pool.max-active}") 71 | private int maxTotal; 72 | @Value("${spring.redis.lettuce.pool.max-wait}") 73 | private long maxWait; 74 | @Value("${spring.redis.host}") 75 | private String host; 76 | @Value("${spring.redis.port}") 77 | private int port; 78 | @Value("${spring.redis.password}") 79 | private String password; 80 | @Value("${spring.redis.database}") 81 | private int database; 82 | 83 | @Bean 84 | public GenericObjectPoolConfig genericObjectPoolConfig() { 85 | GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig<>(); 86 | poolConfig.setMaxIdle(maxIdle); 87 | poolConfig.setMinIdle(minIdle); 88 | poolConfig.setMaxTotal(maxTotal); 89 | poolConfig.setMaxWait(Duration.ofMillis(maxWait)); 90 | return poolConfig; 91 | } 92 | 93 | @Bean(destroyMethod = "shutdown") 94 | @ConditionalOnMissingBean(ClientResources.class) 95 | public DefaultClientResources lettuceClientResources() { 96 | return DefaultClientResources.create(); 97 | } 98 | 99 | @Bean 100 | public RedisStandaloneConfiguration redisSentinelConfiguration() { 101 | RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); 102 | redisStandaloneConfiguration.setDatabase(database); 103 | redisStandaloneConfiguration.setHostName(host); 104 | redisStandaloneConfiguration.setPassword(password); 105 | redisStandaloneConfiguration.setPort(port); 106 | return redisStandaloneConfiguration; 107 | } 108 | 109 | @Bean 110 | public LettuceClientConfiguration lettuceClientConfiguration(GenericObjectPoolConfig genericObjectPoolConfig, ClientResources lettuceClientResources) { 111 | return LettucePoolingClientConfiguration.builder().clientResources(lettuceClientResources).poolConfig(genericObjectPoolConfig).build(); 112 | } 113 | 114 | @Bean 115 | public LettuceConnectionFactory lettuceConnectionFactory(RedisStandaloneConfiguration redisSentinelConfiguration, LettuceClientConfiguration lettuceClientConfiguration) { 116 | return new LettuceConnectionFactory(redisSentinelConfiguration,lettuceClientConfiguration); 117 | } 118 | 119 | @Bean(name = "redisTemplate") 120 | @ConditionalOnMissingBean(name="redisTemplate") 121 | public RedisTemplate redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) { 122 | RedisTemplate redisTemplate = new RedisTemplate<>(); 123 | redisTemplate.setConnectionFactory(lettuceConnectionFactory); 124 | 125 | Jackson2JsonRedisSerializer jsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); 126 | ObjectMapper objectMapper = new ObjectMapper(); 127 | objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); 128 | objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); 129 | SimpleModule simpleModule = new SimpleModule(); 130 | simpleModule.addSerializer(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))); 131 | simpleModule.addSerializer(LocalDate.class,new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT))); 132 | simpleModule.addSerializer(LocalTime.class,new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT))); 133 | simpleModule.addDeserializer(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))); 134 | simpleModule.addDeserializer(LocalDate.class,new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT))); 135 | simpleModule.addDeserializer(LocalTime.class,new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT))); 136 | objectMapper.registerModule(simpleModule); 137 | jsonRedisSerializer.setObjectMapper(objectMapper); 138 | redisTemplate.setKeySerializer(RedisSerializer.string()); 139 | redisTemplate.setHashKeySerializer(RedisSerializer.string()); 140 | redisTemplate.setValueSerializer(jsonRedisSerializer); 141 | redisTemplate.setHashValueSerializer(jsonRedisSerializer); 142 | redisTemplate.afterPropertiesSet();; 143 | return redisTemplate; 144 | } 145 | 146 | @Bean(name = "stringRedisTemplate") 147 | public StringRedisTemplate stringRedisTemplate(LettuceConnectionFactory lettuceConnectionFactory) { 148 | // 重新初始化工厂 149 | lettuceConnectionFactory.afterPropertiesSet(); 150 | return new StringRedisTemplate(lettuceConnectionFactory); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/main/java/io/binghe/redis/config/RedissonConfig.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022-9999 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.binghe.redis.config; 17 | 18 | import cn.hutool.core.util.StrUtil; 19 | import org.redisson.Redisson; 20 | import org.redisson.api.RedissonClient; 21 | import org.redisson.config.ClusterServersConfig; 22 | import org.redisson.config.Config; 23 | import org.redisson.config.SingleServerConfig; 24 | import org.springframework.beans.factory.annotation.Value; 25 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 26 | import org.springframework.context.annotation.Bean; 27 | import org.springframework.context.annotation.Configuration; 28 | 29 | import java.util.Arrays; 30 | 31 | /** 32 | * @author binghe(微信 : hacker_binghe) 33 | * @version 1.0.0 34 | * @description Redisson配置 35 | * @github https://github.com/binghe001 36 | * @copyright 公众号: 冰河技术 37 | */ 38 | @Configuration 39 | public class RedissonConfig { 40 | 41 | @Value("${spring.redis.address}") 42 | private String redisAddress; 43 | 44 | @Value("${spring.redis.password}") 45 | private String password; 46 | 47 | @Value("${spring.redis.database}") 48 | private int database; 49 | 50 | @Bean(name = "redissonClient") 51 | @ConditionalOnProperty(name = "redis.arrange.type", havingValue = "single") 52 | public RedissonClient singleRedissonClient() { 53 | Config config = new Config(); 54 | SingleServerConfig singleServerConfig = config.useSingleServer(); 55 | singleServerConfig.setAddress(redisAddress).setDatabase(database); 56 | if (!StrUtil.isEmpty(password)){ 57 | singleServerConfig.setPassword(password); 58 | } 59 | return Redisson.create(config); 60 | } 61 | 62 | @Bean(name = "redissonClient") 63 | @ConditionalOnProperty(name = "redis.arrange.type", havingValue = "cluster") 64 | public RedissonClient clusterRedissonClient(){ 65 | Config config = new Config(); 66 | ClusterServersConfig clusterServersConfig = config.useClusterServers(); 67 | clusterServersConfig.setNodeAddresses(Arrays.asList(redisAddress)); 68 | if (!StrUtil.isEmpty(password)){ 69 | clusterServersConfig.setPassword(password); 70 | } 71 | return Redisson.create(config); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/io/binghe/redis/lock/DistributedLock.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022-9999 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.binghe.redis.lock; 17 | 18 | import java.util.concurrent.TimeUnit; 19 | 20 | /** 21 | * @author binghe(微信 : hacker_binghe) 22 | * @version 1.0.0 23 | * @description 分布式锁接口 24 | * @github https://github.com/binghe001 25 | * @copyright 公众号: 冰河技术 26 | */ 27 | public interface DistributedLock { 28 | 29 | boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException; 30 | 31 | boolean tryLock(long waitTime, TimeUnit unit) throws InterruptedException; 32 | 33 | boolean tryLock() throws InterruptedException; 34 | 35 | void lock(long leaseTime, TimeUnit unit); 36 | 37 | void unlock(); 38 | 39 | boolean isLocked(); 40 | 41 | boolean isHeldByThread(long threadId); 42 | 43 | boolean isHeldByCurrentThread(); 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/io/binghe/redis/lock/factory/DistributedLockFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022-9999 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.binghe.redis.lock.factory; 17 | 18 | import io.binghe.redis.lock.DistributedLock; 19 | 20 | /** 21 | * @author binghe(微信 : hacker_binghe) 22 | * @version 1.0.0 23 | * @description 分布式锁工程接口 24 | * @github https://github.com/binghe001 25 | * @copyright 公众号: 冰河技术 26 | */ 27 | public interface DistributedLockFactory { 28 | 29 | /** 30 | * 根据key获取分布式锁 31 | */ 32 | DistributedLock getDistributedLock(String key); 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/io/binghe/redis/lock/redisson/RedissonLockFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022-9999 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.binghe.redis.lock.redisson; 17 | 18 | import io.binghe.redis.lock.DistributedLock; 19 | import io.binghe.redis.lock.factory.DistributedLockFactory; 20 | import org.redisson.api.RLock; 21 | import org.redisson.api.RedissonClient; 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | import org.springframework.beans.factory.annotation.Autowired; 25 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 26 | import org.springframework.stereotype.Component; 27 | 28 | import java.util.concurrent.TimeUnit; 29 | 30 | /** 31 | * @author binghe(微信 : hacker_binghe) 32 | * @version 1.0.0 33 | * @description 基于Redisson的分布式锁实现 34 | * @github https://github.com/binghe001 35 | * @copyright 公众号: 冰河技术 36 | */ 37 | @Component 38 | @ConditionalOnProperty(name = "distribute.type.lock", havingValue = "redisson") 39 | public class RedissonLockFactory implements DistributedLockFactory { 40 | private final Logger logger = LoggerFactory.getLogger(RedissonLockFactory.class); 41 | 42 | @Autowired 43 | private RedissonClient redissonClient; 44 | 45 | @Override 46 | public DistributedLock getDistributedLock(String key) { 47 | RLock rLock = redissonClient.getLock(key); 48 | return new DistributedLock() { 49 | @Override 50 | public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException { 51 | boolean isLockSuccess = rLock.tryLock(waitTime, leaseTime, unit); 52 | logger.info("{} get lock result:{}", key, isLockSuccess); 53 | return isLockSuccess; 54 | } 55 | 56 | @Override 57 | public boolean tryLock(long waitTime, TimeUnit unit) throws InterruptedException { 58 | return rLock.tryLock(waitTime, unit); 59 | } 60 | 61 | @Override 62 | public boolean tryLock() throws InterruptedException { 63 | return rLock.tryLock(); 64 | } 65 | 66 | @Override 67 | public void lock(long leaseTime, TimeUnit unit) { 68 | rLock.lock(leaseTime, unit); 69 | } 70 | 71 | @Override 72 | public void unlock() { 73 | if (isLocked() && isHeldByCurrentThread()) { 74 | rLock.unlock(); 75 | } 76 | } 77 | @Override 78 | public boolean isLocked() { 79 | return rLock.isLocked(); 80 | } 81 | 82 | @Override 83 | public boolean isHeldByThread(long threadId) { 84 | return rLock.isHeldByThread(threadId); 85 | } 86 | 87 | @Override 88 | public boolean isHeldByCurrentThread() { 89 | return rLock.isHeldByCurrentThread(); 90 | } 91 | }; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/io/binghe/redis/semaphore/DistributedSemaphore.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022-9999 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.binghe.redis.semaphore; 17 | 18 | import java.util.concurrent.TimeUnit; 19 | 20 | /** 21 | * @author binghe(微信 : hacker_binghe) 22 | * @version 1.0.0 23 | * @description 分布式可过期信号量 24 | * @github https://github.com/binghe001 25 | * @copyright 公众号: 冰河技术 26 | */ 27 | public interface DistributedSemaphore { 28 | 29 | String acquire() throws InterruptedException; 30 | 31 | String acquire(long leaseTime, TimeUnit unit) throws InterruptedException; 32 | 33 | String tryAcquire(); 34 | 35 | String tryAcquire(long waitTime, TimeUnit unit) throws InterruptedException; 36 | 37 | String tryAcquire(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException; 38 | 39 | boolean tryRelease(String permitId); 40 | 41 | void release(String permitId); 42 | 43 | int availablePermits(); 44 | 45 | boolean trySetPermits(int permits); 46 | 47 | void addPermits(int permits); 48 | 49 | boolean updateLeaseTime(String permitId, long leaseTime, TimeUnit unit); 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/io/binghe/redis/semaphore/factory/DistributedSemaphoreFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022-9999 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.binghe.redis.semaphore.factory; 17 | 18 | import io.binghe.redis.semaphore.DistributedSemaphore; 19 | 20 | /** 21 | * @author binghe(微信 : hacker_binghe) 22 | * @version 1.0.0 23 | * @description 分布式可过期信号量工厂类 24 | * @github https://github.com/binghe001 25 | * @copyright 公众号: 冰河技术 26 | */ 27 | public interface DistributedSemaphoreFactory { 28 | 29 | /** 30 | * 根据key获取分布式可过期信号量 31 | */ 32 | DistributedSemaphore getDistributedSemaphore(String key); 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/io/binghe/redis/semaphore/redisson/RedissonSemaphoreFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022-9999 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.binghe.redis.semaphore.redisson; 17 | 18 | import io.binghe.redis.semaphore.DistributedSemaphore; 19 | import io.binghe.redis.semaphore.factory.DistributedSemaphoreFactory; 20 | import org.redisson.api.RPermitExpirableSemaphore; 21 | import org.redisson.api.RedissonClient; 22 | import org.springframework.beans.factory.annotation.Autowired; 23 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 24 | import org.springframework.stereotype.Component; 25 | 26 | import java.util.concurrent.TimeUnit; 27 | 28 | /** 29 | * @author binghe(微信 : hacker_binghe) 30 | * @version 1.0.0 31 | * @description 基于Redisson的分布式可过期信号量工厂 32 | * @github https://github.com/binghe001 33 | * @copyright 公众号: 冰河技术 34 | */ 35 | @Component 36 | @ConditionalOnProperty(name = "distribute.type.semaphore", havingValue = "redisson") 37 | public class RedissonSemaphoreFactory implements DistributedSemaphoreFactory { 38 | 39 | @Autowired 40 | private RedissonClient redissonClient; 41 | 42 | 43 | @Override 44 | public DistributedSemaphore getDistributedSemaphore(String key) { 45 | RPermitExpirableSemaphore permitExpirableSemaphore = redissonClient.getPermitExpirableSemaphore(key); 46 | return new DistributedSemaphore() { 47 | @Override 48 | public String acquire() throws InterruptedException { 49 | return permitExpirableSemaphore.acquire(); 50 | } 51 | 52 | @Override 53 | public String acquire(long leaseTime, TimeUnit unit) throws InterruptedException { 54 | return permitExpirableSemaphore.acquire(leaseTime, unit); 55 | } 56 | 57 | @Override 58 | public String tryAcquire() { 59 | return permitExpirableSemaphore.tryAcquire(); 60 | } 61 | 62 | @Override 63 | public String tryAcquire(long waitTime, TimeUnit unit) throws InterruptedException { 64 | return permitExpirableSemaphore.tryAcquire(waitTime, unit); 65 | } 66 | 67 | @Override 68 | public String tryAcquire(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException { 69 | return permitExpirableSemaphore.tryAcquire(waitTime, leaseTime, unit); 70 | } 71 | 72 | @Override 73 | public boolean tryRelease(String permitId) { 74 | return permitExpirableSemaphore.tryRelease(permitId); 75 | } 76 | 77 | @Override 78 | public void release(String permitId) { 79 | permitExpirableSemaphore.release(permitId); 80 | } 81 | 82 | @Override 83 | public int availablePermits() { 84 | return permitExpirableSemaphore.availablePermits(); 85 | } 86 | 87 | @Override 88 | public boolean trySetPermits(int permits) { 89 | return permitExpirableSemaphore.trySetPermits(permits); 90 | } 91 | 92 | @Override 93 | public void addPermits(int permits) { 94 | permitExpirableSemaphore.addPermits(permits); 95 | } 96 | 97 | @Override 98 | public boolean updateLeaseTime(String permitId, long leaseTime, TimeUnit unit) { 99 | return permitExpirableSemaphore.updateLeaseTime(permitId, leaseTime, unit); 100 | } 101 | }; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/io/binghe/redis/utils/ThreadPoolUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022-9999 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.binghe.redis.utils; 17 | 18 | import java.util.concurrent.*; 19 | 20 | /** 21 | * @author binghe(微信 : hacker_binghe) 22 | * @version 1.0.0 23 | * @description 线程工具类 24 | * @github https://github.com/binghe001 25 | * @copyright 公众号: 冰河技术 26 | */ 27 | public class ThreadPoolUtils { 28 | 29 | private static ThreadPoolExecutor executor = new ThreadPoolExecutor(16, 30 | 16, 31 | 30, 32 | TimeUnit.SECONDS, 33 | new ArrayBlockingQueue<>(4096), 34 | new ThreadPoolExecutor.CallerRunsPolicy()); 35 | /** 36 | * execute task in thread pool 37 | */ 38 | public static void execute(Runnable command){ 39 | executor.execute(command); 40 | } 41 | 42 | public static Future shumit(Callable task){ 43 | return executor.submit(task); 44 | } 45 | 46 | public static void shutdown(){ 47 | if (executor != null){ 48 | executor.shutdown(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: spring-redis 4 | 5 | redis: 6 | database: 0 7 | host: 127.0.0.1 8 | port: 6379 9 | password: 10 | timeout: 30000 11 | lettuce: 12 | pool: 13 | enabled: true 14 | max-active: 8 15 | max-idle: 8 16 | min-idle: 0 17 | max-wait: 5000 18 | address: redis://127.0.0.1:6379 19 | 20 | 21 | cache: 22 | type: 23 | local: guava 24 | distribute: redis 25 | 26 | distribute: 27 | type: 28 | lock: redisson 29 | semaphore: redisson 30 | 31 | 32 | redis: 33 | arrange: 34 | type: single # single or cluster -------------------------------------------------------------------------------- /src/test/java/io/binghe/redis/test/DistributeCacheServiceTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022-9999 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.binghe.redis.test; 17 | 18 | import cn.hutool.json.JSONUtil; 19 | import io.binghe.redis.cache.distribute.DistributeCacheService; 20 | import io.binghe.redis.test.bean.User; 21 | import org.junit.Test; 22 | import org.junit.runner.RunWith; 23 | import org.springframework.beans.factory.annotation.Autowired; 24 | import org.springframework.boot.test.context.SpringBootTest; 25 | import org.springframework.test.context.junit4.SpringRunner; 26 | 27 | import java.util.Arrays; 28 | import java.util.List; 29 | import java.util.concurrent.TimeUnit; 30 | 31 | /** 32 | * @author binghe(微信 : hacker_binghe) 33 | * @version 1.0.0 34 | * @description 测试分布式缓存 35 | * @github https://github.com/binghe001 36 | * @copyright 公众号: 冰河技术 37 | */ 38 | @SpringBootTest 39 | @RunWith(SpringRunner.class) 40 | public class DistributeCacheServiceTest { 41 | 42 | @Autowired 43 | private DistributeCacheService distributeCacheService; 44 | 45 | 46 | @Test 47 | public void testQueryWithPassThrough(){ 48 | User user = distributeCacheService.queryWithPassThrough("pass:through:", 1002852L, User.class, this::getUser, 60L, TimeUnit.SECONDS); 49 | System.out.println(JSONUtil.toJsonStr(user)); 50 | } 51 | @Test 52 | public void testQueryWithPassThroughWithoutArgs(){ 53 | User user = distributeCacheService.queryWithPassThroughWithoutArgs("pass:through001:", User.class, this::getUserWithoutArgs, 60L, TimeUnit.SECONDS); 54 | System.out.println(JSONUtil.toJsonStr(user)); 55 | } 56 | 57 | @Test 58 | public void testQuerySimpleDataWithPassThrough(){ 59 | Integer id = distributeCacheService.queryWithPassThrough("pass:through2:", 100285210, Integer.class, this::getId, 60L, TimeUnit.SECONDS); 60 | System.out.println(id); 61 | } 62 | @Test 63 | public void testQuerySimpleDataWithPassThroughWithoutArgs(){ 64 | Integer id = distributeCacheService.queryWithPassThroughWithoutArgs("pass:through2002:", Integer.class, this::getIdWithoutArgs, 60L, TimeUnit.SECONDS); 65 | System.out.println(id); 66 | } 67 | 68 | @Test 69 | public void testQueryWithPassThroughList(){ 70 | List list = distributeCacheService.queryWithPassThroughList("pass:through:list:", null, User.class, this::getUserList, 60L, TimeUnit.SECONDS); 71 | System.out.println(JSONUtil.toJsonStr(list)); 72 | } 73 | @Test 74 | public void testQueryWithPassThroughListWithoutArgs(){ 75 | List list = distributeCacheService.queryWithPassThroughListWithoutArgs("pass:through:list003:", User.class, this::getUserListWithoutArgs, 60L, TimeUnit.SECONDS); 76 | System.out.println(JSONUtil.toJsonStr(list)); 77 | } 78 | 79 | @Test 80 | public void testQuerySimpleDataWithPassThroughList(){ 81 | List list = distributeCacheService.queryWithPassThroughList("pass:through:list2:", 100285211, Integer.class, this::getIds, 60L, TimeUnit.SECONDS); 82 | System.out.println(JSONUtil.toJsonStr(list)); 83 | } 84 | @Test 85 | public void testQuerySimpleDataWithPassThroughListWithoutArgs(){ 86 | List list = distributeCacheService.queryWithPassThroughListWithoutArgs("pass:through:list2004:", Integer.class, this::getIdsWithoutArgs, 60L, TimeUnit.SECONDS); 87 | System.out.println(JSONUtil.toJsonStr(list)); 88 | } 89 | 90 | @Test 91 | public void testQueryWithLogicalExpire(){ 92 | User user = distributeCacheService.queryWithLogicalExpire("logical:expire:", 1002852L, User.class, this::getUser, 60L, TimeUnit.SECONDS); 93 | System.out.println(JSONUtil.toJsonStr(user)); 94 | } 95 | 96 | @Test 97 | public void testQueryWithLogicalExpireWithoutArgs(){ 98 | User user = distributeCacheService.queryWithLogicalExpireWithoutArgs("logical:expire005:", User.class, this::getUserWithoutArgs, 60L, TimeUnit.SECONDS); 99 | System.out.println(JSONUtil.toJsonStr(user)); 100 | } 101 | 102 | @Test 103 | public void testQuerySimpleDataWithLogicalExpire(){ 104 | Integer id = distributeCacheService.queryWithLogicalExpire("logical:expire2:", 100285212, Integer.class, this::getId, 60L, TimeUnit.SECONDS); 105 | System.out.println(id); 106 | } 107 | @Test 108 | public void testQuerySimpleDataWithLogicalExpireWithoutArgs(){ 109 | Integer id = distributeCacheService.queryWithLogicalExpireWithoutArgs("logical:expire2006:", Integer.class, this::getIdWithoutArgs, 60L, TimeUnit.SECONDS); 110 | System.out.println(id); 111 | } 112 | 113 | @Test 114 | public void testQueryWithLogicalExpireList(){ 115 | List list = distributeCacheService.queryWithLogicalExpireList("logical:expire:list:", null, User.class, this::getUserList, 60L, TimeUnit.SECONDS); 116 | System.out.println(JSONUtil.toJsonStr(list)); 117 | } 118 | @Test 119 | public void testQueryWithLogicalExpireListWithoutArgs(){ 120 | List list = distributeCacheService.queryWithLogicalExpireListWithoutArgs("logical:expire:list007:", User.class, this::getUserListWithoutArgs, 60L, TimeUnit.SECONDS); 121 | System.out.println(JSONUtil.toJsonStr(list)); 122 | } 123 | 124 | @Test 125 | public void testQuerySimpleDataWithLogicalExpireList(){ 126 | List list = distributeCacheService.queryWithLogicalExpireList("logical:expire:list2:", 100285213, Integer.class, this::getIds, 60L, TimeUnit.SECONDS); 127 | System.out.println(JSONUtil.toJsonStr(list)); 128 | } 129 | @Test 130 | public void testQuerySimpleDataWithLogicalExpireListWithoutArgs(){ 131 | List list = distributeCacheService.queryWithLogicalExpireListWithoutArgs("logical:expire:list2008:", Integer.class, this::getIdsWithoutArgs, 60L, TimeUnit.SECONDS); 132 | System.out.println(JSONUtil.toJsonStr(list)); 133 | } 134 | 135 | @Test 136 | public void testQueryWithMutex(){ 137 | User user = distributeCacheService.queryWithMutex("mutex:", 1002852L, User.class, this::getUser, 60L, TimeUnit.SECONDS); 138 | System.out.println(JSONUtil.toJsonStr(user)); 139 | } 140 | @Test 141 | public void testQueryWithMutexWithoutArgs(){ 142 | User user = distributeCacheService.queryWithMutexWithoutArgs("mutex009:", User.class, this::getUserWithoutArgs, 60L, TimeUnit.SECONDS); 143 | System.out.println(JSONUtil.toJsonStr(user)); 144 | } 145 | 146 | @Test 147 | public void testQuerySimpleDataWithMutex(){ 148 | Integer id = distributeCacheService.queryWithMutex("mutex2:", 100285214, Integer.class, this::getId, 60L, TimeUnit.SECONDS); 149 | System.out.println(id); 150 | } 151 | @Test 152 | public void testQuerySimpleDataWithMutexWithoutArgs(){ 153 | Integer id = distributeCacheService.queryWithMutexWithoutArgs("mutex2010:", Integer.class, this::getIdWithoutArgs, 60L, TimeUnit.SECONDS); 154 | System.out.println(id); 155 | } 156 | 157 | @Test 158 | public void testQueryWithMutexList(){ 159 | List list = distributeCacheService.queryWithMutexList("mutex:list:", null, User.class, this::getUserList, 60L, TimeUnit.SECONDS); 160 | System.out.println(JSONUtil.toJsonStr(list)); 161 | } 162 | @Test 163 | public void testQueryWithMutexListWithoutArgs(){ 164 | List list = distributeCacheService.queryWithMutexListWithoutArgs("mutex:list011:", User.class, this::getUserListWithoutArgs, 60L, TimeUnit.SECONDS); 165 | System.out.println(JSONUtil.toJsonStr(list)); 166 | } 167 | 168 | @Test 169 | public void testQuerySimpleDataWithMutexList(){ 170 | List list = distributeCacheService.queryWithMutexList("mutex:list2:", 123, Integer.class, this::getIds, 60L, TimeUnit.SECONDS); 171 | System.out.println(JSONUtil.toJsonStr(list)); 172 | } 173 | 174 | @Test 175 | public void testQuerySimpleDataWithMutexListWithoutArgs(){ 176 | List list = distributeCacheService.queryWithMutexListWithoutArgs("mutex:list2012:", Integer.class, this::getIdsWithoutArgs, 60L, TimeUnit.SECONDS); 177 | System.out.println(JSONUtil.toJsonStr(list)); 178 | } 179 | 180 | /** 181 | * 模拟带参数从数据库查询对象 182 | */ 183 | public User getUser(Long id){ 184 | return new User(id, "binghe"); 185 | } 186 | 187 | /** 188 | * 默认不带参数从数据库查询对象 189 | */ 190 | public User getUserWithoutArgs(){ 191 | return new User(1L, "binghe"); 192 | } 193 | 194 | /** 195 | * 模拟带参数查询从数据库对象列表 196 | */ 197 | public List getUserList(String type){ 198 | return Arrays.asList( 199 | new User(1L, "binghe001"), 200 | new User(2L, "binghe002"), 201 | new User(3L, "binghe003") 202 | ); 203 | } 204 | 205 | /** 206 | * 模拟不带参数从数据库查询对象列表 207 | */ 208 | public List getUserListWithoutArgs(){ 209 | return Arrays.asList( 210 | new User(1L, "binghe001"), 211 | new User(2L, "binghe002"), 212 | new User(3L, "binghe003") 213 | ); 214 | } 215 | 216 | /** 217 | * 模拟带参数从数据库查询简单数据类型数据 218 | */ 219 | public Integer getId(Integer id){ 220 | return id; 221 | } 222 | 223 | /** 224 | * 模拟不带参数从数据库查询简单数据类型数据 225 | */ 226 | public Integer getIdWithoutArgs(){ 227 | return 0; 228 | } 229 | 230 | /** 231 | * 模拟带参数从数据库查询简单数据类型数据列表 232 | */ 233 | public List getIds(Integer id){ 234 | return Arrays.asList(0,0,0); 235 | } 236 | 237 | /** 238 | * 模拟不带参数从数据库查询简单数据类型数据列表 239 | */ 240 | public List getIdsWithoutArgs(){ 241 | return Arrays.asList(0,0,0); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/test/java/io/binghe/redis/test/bean/User.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022-9999 the original author or authors. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.binghe.redis.test.bean; 17 | 18 | import cn.hutool.json.JSONUtil; 19 | 20 | /** 21 | * @author binghe(微信 : hacker_binghe) 22 | * @version 1.0.0 23 | * @description 测试对象 24 | * @github https://github.com/binghe001 25 | * @copyright 公众号: 冰河技术 26 | */ 27 | public class User { 28 | 29 | private Long id; 30 | private String name; 31 | 32 | public User() { 33 | } 34 | 35 | public User(Long id, String name) { 36 | this.id = id; 37 | this.name = name; 38 | } 39 | 40 | public Long getId() { 41 | return id; 42 | } 43 | 44 | public void setId(Long id) { 45 | this.id = id; 46 | } 47 | 48 | public String getName() { 49 | return name; 50 | } 51 | 52 | public void setName(String name) { 53 | this.name = name; 54 | } 55 | 56 | @Override 57 | public String toString() { 58 | return JSONUtil.toJsonStr(this); 59 | } 60 | } 61 | --------------------------------------------------------------------------------