到目前為止赵讯,這篇就是關(guān)于SpringBoot入門學(xué)習(xí)的最后一篇文章了,學(xué)完這三篇文章對 SpringBoot 也就有了一個初步的了解赁温。
「 前言 」
本文的主要內(nèi)容:
- 事務(wù)處理
- Docker安裝及常用命令
- 接入Redis緩存及配置Session
- 整合MongoDB
- 配置開發(fā)與生產(chǎn)環(huán)境
- 部署項目到Docker上
「 事務(wù)處理 」
關(guān)于事務(wù)疗琉,可以簡單理解為,當(dāng)執(zhí)行多條數(shù)據(jù)操作時钦椭,能確保每條操作能同時執(zhí)行成功,否則有一條失敗就會回滾前面所有執(zhí)行成功的操作,保證一致性玉凯。下面我們來做一個簡單的例子势腮。
@Service
public class MyUserServices {
@Resource
private MyUserMapper userMapper;
@Transactional(rollbackFor = IllegalArgumentException.class, noRollbackFor = IllegalStateException.class)
public MyUser insertUser(MyUser user) {
userMapper.insertUser(user);
if (userMapper.selectUserByName2(user.getUserName()).size() >= 2) {
throw new IllegalArgumentException("userName " +user.getUserName() + " is all through exist");
}
if (user.getPassword().equals("123456")) {
throw new IllegalStateException("can't insert password 123456");
}
return user;
}
}
可以看到我們給inserUser
方法添加了Transactional
注解联贩,里面包含了rollbackFor
和noRollbackFor
兩個屬性漫仆,通過字面上我們可以看出,一個是回滾一個是不回滾泪幌,value值都是接收異常class盲厌。可以這樣理解祸泪,當(dāng)方法中拋出rollbackFor
定義的異常則會執(zhí)行事務(wù)回滾吗浩,當(dāng)方法中拋出noRollbackFor
定義的異常則不會執(zhí)行事務(wù)回滾。后面有兩個判斷没隘,一個是通過判斷如果插入了兩個相同的用戶名懂扼,則拋出異常進(jìn)行回滾,實際上開發(fā)中不會這樣去做右蒲,這里是為了學(xué)習(xí)事務(wù)處理來做的阀湿,另一個是通過判斷密碼為123456拋出異常但不進(jìn)行回滾。
「 Docker安裝及常用命令 」
Docker是一個開源的應(yīng)用容器引擎瑰妄,基于Go語言并遵從Apache2.0協(xié)議開源陷嘴。可以讓開發(fā)者打包他們的應(yīng)用以及依賴包到一個輕量級间坐、可移植的容器中灾挨,然后發(fā)布到任何流行的Linux機器上,也可以實現(xiàn)虛擬化竹宋。容器是完全使用沙箱機制劳澄,相互之間不會有任何接口(類似iPhone的app),更重要的是容器性能開銷極低。
Docker下載地址:
Mac下載:https://download.docker.com/mac/stable/Docker.dmg
Windows下載:https://download.docker.com/win/stable/Docker%20for%20Windows%20Installer.exe
安裝完之后蜈七,運行下面命令浴骂,驗證是否安裝成功
docker version
# 或者
docker info
常用的docker命令如下:
# 搜索image
docker search [imageNmae]
# 拉取 image
docker pull [imageName]
# 列出本機所有 image
docker image ls
docker images
# 查看 image 信息
docker images [imageName]
# 強制刪除 image
docker rmi -f [imageId]
# 后臺運行容器
docker run -d [imageName]
# 查看正在運行的容器
docker ps
# 殺掉容器
docker kill [containerId]
# 在運行的容器中執(zhí)行命令
docker exec -it [containerId] [cmd]
# 強制刪除容器
docker rm -f [containerId]
「 接入Redis緩存及配置Session 」
通過Docker添加Redis, 兩行命令完成, pull命令時間可能會比較久,需要耐心等待宪潮。
# 拉取redis鏡像文件
docker pull redis
# 運行redis容器
# -p 6379:6379: 將容器的6379端口映射到主機的6379端口
# -v $PWD/data:/data: 將主機中當(dāng)前目錄下的data掛載到容器的/data
# -d redis redis-server --appendonly yes: 在容器中后臺執(zhí)行redis-server啟動命令溯警,并打開redis持久化配置
docker run -p 6379:6379 -v $PWD/data:/data -d redis redis-server --appendonly yes
接下來添加Redis依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置application.yml, 添加spring cache和redis配置, 密碼默認(rèn)為空:
spring:
cache:
type: redis
cache-names: soaic
redis:
database: 0
host: 192.168.0.184
port: 6379
password:
jedis:
pool:
#連接池支持的最大連接數(shù)
max-active: 1000
#連接池中連接用完時,新的請求等待時間,毫秒
max-wait: -1ms
#連接池中最多可空閑maxIdle個連接
max-idle: 400
min-idle: 0
timeout: 1000ms
MyUserServices核心代碼:
@Service
public class MyUserServices {
@Resource
private MyUserMapper userMapper;
@CachePut(value = "user", key = "#user.id") //如果方法參數(shù)為對象,并且不指定key狡相,需要重寫toString方法
@Transactional(rollbackFor = IllegalArgumentException.class, noRollbackFor = IllegalStateException.class)
public MyUser insertUser(MyUser user) {
userMapper.insertUser(user);
System.out.println("添加緩存key為"+user.getId());
if (userMapper.selectUserByName2(user.getUserName()).size() >= 2) {
throw new IllegalArgumentException("userName " +user.getUserName() + " is all through exist");
}
if (user.getPassword().equals("123456")) {
throw new IllegalStateException("can't insert password 123456");
}
return user;
}
@Cacheable(value = "user", key="id", condition="#id>0") //不指定key梯轻,默認(rèn)以方法參數(shù)為key
public MyUser selectUser(Integer id) {
MyUser user = userMapper.selectUser(id);
System.out.println("添加緩存key為" + id);
return user;
}
@CacheEvict(value = "user")
public Integer removeUser(Integer id) {
Integer result = userMapper.deleteUser(id);
System.out.println("刪除緩存key為" + id);
return result;
}
}
注解@CachePut()
表示當(dāng)有緩存刷新緩存,沒有則添加緩存尽棕,有三個屬性喳挑,value
為緩存的名稱為application.yml配置的cache-names;key
為緩存的key, 如果不指定key,默認(rèn)以方法參數(shù)為key,如果方法參數(shù)為對象伊诵,需要重寫toString方法;condition
為緩存的條件单绑,條件為true時才進(jìn)行緩存。
注解@Cacheable()
表示當(dāng)有緩存則取緩存曹宴,沒有則添加緩存搂橙,三個屬性和注解@CachePut()屬性一致。
注解@CacheEvict()
表示當(dāng)有緩存則清除緩存笛坦,除了前面說的三個屬性外区转,還多出兩個屬性,allEntries
為true清除所有元素版扩;beforeInvocation
為true時表示在調(diào)用該方法之前清除緩存中的指定元素废离,默認(rèn)是方法成功執(zhí)行之后觸發(fā),即方法如果因為拋出異常而未能成功返回時也不會觸發(fā)清除操作礁芦。
接著添加RedisCacheConfig配置key生成規(guī)則蜻韭、配置RedisTemplate和緩存序列化以json格式存儲
@Configuration
@EnableCaching
public class RedisCacheConfig extends CachingConfigurerSupport {
/**
* 配置redis key
*/
@Bean
@Override
public KeyGenerator keyGenerator() {
return (target, method, params) -> {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
for (Object obj : params) {
sb.append(":");
sb.append(obj.toString());
}
return sb.toString();
};
}
/**
* RedisTemplate 序列化
*/
@Bean
public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
Jackson2JsonRedisSerializer redisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
redisSerializer.setObjectMapper(objectMapper);
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setValueSerializer(redisSerializer);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
/**
* redis 緩存序列化
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory){
Jackson2JsonRedisSerializer<Object> redisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
redisSerializer.setObjectMapper(objectMapper);
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer));
return RedisCacheManager.builder(connectionFactory).cacheDefaults(cacheConfiguration).build();
}
}
最后把MyUser
實現(xiàn)Serializable
, 否則會報序列化錯誤
public class MyUser implements Serializable {
...
}
測試。先通過Controller(這里沒有寫出代碼柿扣,可以到github上查看)調(diào)用MyUserServices中方法肖方,然后進(jìn)入docker運行的容器查看
# 查看正在運行的容器ID
docker ps
# 進(jìn)入redis容器中
docker exec -it [containerId] redis-cli
# 進(jìn)入之后輸入 info 可以查看redis信息
info
# 查看所有key
keys *
# 查看某個key的值
get key
接下來就是配置Session了。為什么要用redis配置session窄刘?有這么幾點原因:
1窥妇、方便管理session
2、托管到redis共享Session方便使用集群, 即一臺服務(wù)器壞了娩践,切換到另一臺服務(wù)器活翩,無需再次登錄還能正常訪問
首先添加依賴
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
要注意一下的是,如果spring-boot-starter-parent
版本是2.1.5.RELEASE時翻伺,添加依賴后運行會報下面這個錯誤材泄,解決辦法可以把版本降低,改成2.1.4.RELEASE即可吨岭。
java.lang.IllegalStateException: Error processing condition on org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration.taskScheduler
配置application.yml, 設(shè)置session存儲方式
spring:
session:
store-type: redis
添加RedisSessionConfig配置拉宗,啟用Session, 并設(shè)置失效時間
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 86400*30)//原 Spring Boot 的 server.session.timeout 屬性不再生效。
public class RedisSessionConfig {
}
接下來配置Session攔截器以及Login接口中登錄成功設(shè)置session辣辫,上篇文章中介紹的還比較詳細(xì)旦事,這里就簡單的貼一下核心代碼:
SessionInterceptor核心代碼
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("SessionInterceptor preHandle");
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("user") != null) {
return true;
} else {
PrintWriter printWriter = response.getWriter();
printWriter.write("{code: 501, message:\"not login!\"}");
return false;
}
}
MyUserController核心代碼
@RequestMapping(value = "/login", method = RequestMethod.GET)
public ResponseResult<MyUser> login(HttpServletRequest request, String userName, String password) {
ResponseResult<MyUser> responseResult;
try {
List<MyUser> myUser = myUserServices.login(userName, password);
if (myUser != null && myUser.size() > 0) {
request.getSession(true).setAttribute("user", myUser.get(0));
responseResult = new ResponseResult<>(200, "login success", myUser.get(0));
} else {
responseResult = new ResponseResult<>(501, "login failure: invalid userName or password", null);
}
} catch (Exception e) {
e.printStackTrace();
responseResult = new ResponseResult<>(501, "login failure: " + e.getMessage(), null);
}
return responseResult;
}
最后測試,當(dāng)?shù)谝粫r間訪問其它接口會報501錯誤急灭,而當(dāng)訪問login登錄成功后姐浮,則會保存session,后面其它接口訪問都可以正常葬馋。這里沒有用多臺服務(wù)器測試卖鲤,只測試了下肾扰,當(dāng)服務(wù)器停止再啟動后,訪問接口都不用再重新登錄蛋逾,session存儲在redis中集晚,不會因為服務(wù)器斷開而消失,這就區(qū)別于不托管redis区匣,session是存儲在內(nèi)存中偷拔,每次停止服務(wù)器再啟動都要重新登錄。
「 整合MongoDB 」
通過Docker添加MongoDB, 同樣兩行命令完成沉颂。
docker pull mongo
# -p 27017:27017: 將容器的27017 端口映射到主機的27017 端口
# -v $PWD/db:/data/db: 將主機中當(dāng)前目錄下的db掛載到容器的/data/db条摸,作為mongo數(shù)據(jù)存儲目錄
docker run -p 27017:27017 -v $PWD/db:/data/db -d mongo
添加MongoDB依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
配置application.yml, 這里用Docker添加的所以沒有賬號密碼悦污,當(dāng)有賬號密碼時铸屉,url格式為:mongodb://user:pwd@ip:port/soaic?maxPoolSize=256; 當(dāng)有多臺數(shù)據(jù)庫時,url格式為:mongodb://user:pwd@ip1:port1,ip2:port2/database?maxPoolSize=512
spring:
data:
mongodb:
uri: mongodb://localhost:27017/soaic?maxPoolSize=256
修改MyUser對象, 在類作用域上添加@Document
注解定義為一個文檔,切端,也可以理解為是一張表彻坛,注解@Id
定義屬性為ID, 注解@Field
定義為存儲表中的字段名
@Document("myUser")
public class MyUser implements Serializable {
@Id
private String id;
private String userName;
private String password;
@Field("roles") //在文檔中的名稱為roles, 以數(shù)組形式存儲
private Collection<Role> roles = new LinkedHashSet<>();
}
添加 Role 對象
public class Role {
private String roleName;
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
}
在SpringBoot中對mongoDB操作數(shù)據(jù)有多種方式,如:通過繼承MongoRepository
對象調(diào)用定義好的方法, 或者遵從定義方法名的規(guī)則踏枣,或者通過注解@Query
實現(xiàn)查詢昌屉,?0
為占位符取方法參數(shù)的第一個,依次類推
public interface MyUserRepository extends MongoRepository<MyUser, String> {
List<MyUser> findByUserName(String name);
@Query("{'userName': ?0}")
List<MyUser> withQueryUserName(String name);
}
通過MongoTemplate
來實現(xiàn), 下面代碼簡單的實現(xiàn)了增刪改查CRUD
@Service
public class MyUserDaoImpl implements MyUserDAO{
@Autowired
MongoTemplate mongoTemplate;
@Override
public List<MyUser> findByUserName(String userName) {
Query query = new Query(Criteria.where("userName").is(userName));
return mongoTemplate.find(query, MyUser.class);
}
@Override
public MyUser insertUser(MyUser myUser) {
return mongoTemplate.insert(myUser);
}
@Override
public boolean deleteUser(String id) {
Query query = new Query(Criteria.where("id").is(id));
DeleteResult result = mongoTemplate.remove(query, MyUser.class);
return result.getDeletedCount() > 0;
}
@Override
public boolean updateUser(MyUser myUser) {
Criteria criteria= Criteria.where("id").is(myUser.getId());
Update update = new Update();
if (myUser.getUserName() != null)
update.set("userName", myUser.getUserName());
if (myUser.getPassword() != null)
update.set("password", myUser.getPassword());
UpdateResult result = mongoTemplate.updateFirst(new Query(criteria), update, MyUser.class);
return result.getModifiedCount() > 0;
}
}
通過單元測試對上面的代碼進(jìn)行測試, 在test/java/com.soaic.hellospringboot中創(chuàng)建MongoDBTests, 添加注解@SpringBootTest
和@RunWith(SpringRunner.class)
, 測試的方法添加@Test
注解茵瀑,然后分別點擊方法左邊的綠色小圖標(biāo) Run Test 即可
@RunWith(SpringRunner.class)
@SpringBootTest
public class MongoDBTests {
@Autowired
private MyUserRepository myUserRepository;
@Autowired
private MyUserDaoImpl myUserDaoImpl;
@Test
public void testSave() {
MyUser myUser = new MyUser();
myUser.setUserName("Soaic");
myUser.setPassword("123456");
Collection<Role> roles = new LinkedHashSet<>();
Role role = new Role();
role.setRoleName("管理員");
roles.add(role);
Role role1 = new Role();
role1.setRoleName("程序員");
roles.add(role1);
myUser.setRoles(roles);
//myUserRepository.save(myUser);
myUserDaoImpl.insertUser(myUser);
}
@Test
public void testFind() {
List<MyUser> myUserList = myUserDaoImpl.findByUserName("Soaic");
System.out.println(JSON.toJSONString(myUserList));
}
@Test
public void testUpdate() {
List<MyUser> myUserList = myUserRepository.withQueryUserName("Soaic");
for (MyUser myUser: myUserList) {
myUser.setPassword("1234567");
myUserDaoImpl.updateUser(myUser);
}
}
@Test
public void testDel() {
List<MyUser> myUserList = myUserRepository.findByUserName("Soaic");
for (MyUser myUser: myUserList) {
myUserDaoImpl.deleteUser(myUser.getId());
}
}
}
查詢mongoDB數(shù)據(jù)庫數(shù)據(jù)有無變化间驮,可以通過如下命令連接mongoDB查詢
# 連接mongodb
docker run -it mongo mongo --host 172.17.0.1
# 查看所有數(shù)據(jù)庫
show dbs
# 切換數(shù)據(jù)庫
use soaic
# 查看數(shù)據(jù)庫狀態(tài)
db.stats()
# 查詢數(shù)據(jù)庫下所有表
show collections
# 查詢某個表的數(shù)據(jù)
db.collection.find()
「 配置開發(fā)與生產(chǎn)環(huán)境 」
添加application-dev.yml
開發(fā)環(huán)境配置和application-prod.yml
生產(chǎn)環(huán)境配置, 如果我們想使用生產(chǎn)環(huán)境马昨,因為程序會默認(rèn)加載application.yml
, 所以只需要在里面配置spring.profiles.active
為prod
即可竞帽,配置為dev
則為開發(fā)環(huán)境。
spring:
profiles:
active: prod
「 部署項目到Docker上 」
部署到Docker上需要先把項目打包成jar包鸿捧,可以通過idea中的 Maven Projects 找到Lifecycle下的clean 和 package 依次雙擊執(zhí)行(也可以執(zhí)行命令mvn clean package
)屹篓,最后就可以在target文件夾下找到hellospringboot-0.0.1-SNAPSHOT.jar
如果不部署到Docker上,可以直接運行下面命令啟動項目
java -jar hellospringboot-0.0.1-SNAPSHOT.jar
如果部署到docker上匙奴,我們需要創(chuàng)建一個Dockerfile文件在項目的根目錄堆巧,里面內(nèi)容如下:
FROM java:8
MAINTAINER Soaic
ADD target/hellospringboot-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8443
EXPOSE 8088
ENTRYPOINT ["java", "-jar", "/app.jar"]
第一行:基于鏡像為Java, 標(biāo)簽版本為8
第二行:作者Soaic
第三行:將hellospringboot-0.0.1-SNAPSHOT.jar添加到鏡像中,并重命名為app.jar
第四行和第五行:運行鏡像的容器泼菌,監(jiān)聽8443和8088端口
第六行:啟動時運行 java -jar app.jar
接下來編譯鏡像谍肤,在項目根目錄下執(zhí)行下面命令,其中hellospringboot為鏡像名稱哗伯,最后一個"."荒揣,用來指明Dockerfile路徑,表示在當(dāng)前路路徑下笋颤,編譯第一次需要下載java8乳附,后面編譯就不需要下載了
docker build -t hellospringboot .
編譯完成后内地,可以通過下面命令查看及運行項目
# 查看是否有一個image為hellospringboot
docker images
# 后臺運行項目,并映射兩個端口號8443和8088赋除,--name為修改運行容器名稱可加可不加默認(rèn)為image名稱
docker run -d --name hellospringboot -p 8443:8443 -p 8088:8088 hellospringboot
最后可以通過 http://localhost:8088 和 https://localhost:8443 這個兩個地址訪問了阱缓。
本文所有源碼都已放在github上:https://github.com/soaic/HelloSpringBoot
以上就是這篇文章的全部內(nèi)容,希望對大家能有所幫助举农,如有疑問或建議歡迎大家留言交流~