#294 (리뷰/메모): - MemoService.upsert: 동시성 INSERT 시 DuplicateKeyException 폴백 → UPDATE - ReviewService.toggleFavorite: 동시성 INSERT 시 DuplicateKeyException ignored (토글 ON) - ReviewController: rating(0~5) Bean validation 헬퍼, body.rating null/비숫자 → 400 - ReviewMapper.xml getAvgRating: NVL로 0건 시에도 0.0 보장 #295 (채널): - ChannelController.create: typed DataIntegrityViolationException으로 유니크 충돌 감지 (제약명 문자열 매칭 폐기) - ChannelController.create: channel_id/channel_name null/빈값 → 400 - ChannelService.deactivate: "UC..." 형식 검증으로 명시적 분기 (이전 폴백 방식의 의도 모호함 해결) - ChannelMapper.xml findByChannelId: description/tags/sort_order까지 SELECT #290 (식당 CRUD): - RestaurantController: @PreDestroy로 virtual thread executor shutdown - RestaurantController: 캐시 역직렬화 실패를 silent ignore → log.warn + cache.del 자동 evict - RestaurantController: setTablingUrl/setCatchtableUrl URL 스킴 화이트리스트 검증 - CacheService: 단일 키 del() 메서드 추가 후속 분리: - #333 (#290 DTO 화이트리스트 + DDG 대체) - #334 (#295 cache.flush 세분화 + scan 비동기) - #335 (#294 테스트) Refs: #290 #294 #295
54 lines
2.2 KiB
XML
54 lines
2.2 KiB
XML
<?xml version="1.0" encoding="UTF-8" ?>
|
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
|
<mapper namespace="com.tasteby.mapper.ChannelMapper">
|
|
|
|
<resultMap id="channelResultMap" type="com.tasteby.domain.Channel">
|
|
<id property="id" column="id"/>
|
|
<result property="channelId" column="channel_id"/>
|
|
<result property="channelName" column="channel_name"/>
|
|
<result property="titleFilter" column="title_filter"/>
|
|
<result property="description" column="description"/>
|
|
<result property="tags" column="tags"/>
|
|
<result property="sortOrder" column="sort_order"/>
|
|
<result property="videoCount" column="video_count"/>
|
|
<result property="lastVideoAt" column="last_video_at"/>
|
|
</resultMap>
|
|
|
|
<select id="findAllActive" resultMap="channelResultMap">
|
|
SELECT c.id, c.channel_id, c.channel_name, c.title_filter, c.description, c.tags, c.sort_order,
|
|
(SELECT COUNT(*) FROM videos v WHERE v.channel_id = c.id) AS video_count,
|
|
(SELECT MAX(v.published_at) FROM videos v WHERE v.channel_id = c.id) AS last_video_at
|
|
FROM channels c
|
|
WHERE c.is_active = 1
|
|
ORDER BY c.sort_order, c.channel_name
|
|
</select>
|
|
|
|
<insert id="insert">
|
|
INSERT INTO channels (id, channel_id, channel_name, title_filter)
|
|
VALUES (#{id}, #{channelId}, #{channelName}, #{titleFilter})
|
|
</insert>
|
|
|
|
<update id="deactivateByChannelId">
|
|
UPDATE channels SET is_active = 0
|
|
WHERE channel_id = #{channelId} AND is_active = 1
|
|
</update>
|
|
|
|
<update id="deactivateById">
|
|
UPDATE channels SET is_active = 0
|
|
WHERE id = #{id} AND is_active = 1
|
|
</update>
|
|
|
|
<update id="updateChannel">
|
|
UPDATE channels SET description = #{description}, tags = #{tags}, sort_order = #{sortOrder}
|
|
WHERE id = #{id}
|
|
</update>
|
|
|
|
<select id="findByChannelId" resultMap="channelResultMap">
|
|
<!-- #295 — findAllActive와 동일하게 description/tags/sort_order까지 SELECT -->
|
|
SELECT id, channel_id, channel_name, title_filter, description, tags, sort_order
|
|
FROM channels
|
|
WHERE channel_id = #{channelId} AND is_active = 1
|
|
</select>
|
|
|
|
</mapper>
|