Migrate to MyBatis with proper Controller→Service→Mapper layering
- Add MyBatis Spring Boot Starter with XML mappers and domain classes - Create 9 mapper interfaces + XML: Restaurant, Video, Channel, Review, User, Stats, DaemonConfig, Search, Vector - Create 10 domain classes with Lombok: Restaurant, VideoSummary, VideoDetail, VideoRestaurantLink, Channel, Review, UserInfo, DaemonConfig, SiteVisitStats, VectorSearchResult - Create 7 new service classes: RestaurantService, VideoService, ChannelService, ReviewService, UserService, StatsService, DaemonConfigService - Refactor all controllers to be thin (HTTP + auth only), delegating business logic to services - Refactor SearchService, PipelineService, DaemonScheduler, AuthService, YouTubeService to use mappers/services instead of JDBC/repositories - Add Jackson SNAKE_CASE property naming for consistent API responses - Add ClobTypeHandler for Oracle CLOB→String in MyBatis - Add IdGenerator utility for centralized UUID generation - Delete old repository/ package (6 files), JdbcConfig, LowerCaseKeyAdvice - VectorService retains JDBC for Oracle VECTOR type support Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
<?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.RestaurantMapper">
|
||||
|
||||
<!-- ===== Result Maps ===== -->
|
||||
|
||||
<resultMap id="restaurantMap" type="Restaurant">
|
||||
<id property="id" column="id"/>
|
||||
<result property="name" column="name"/>
|
||||
<result property="address" column="address"/>
|
||||
<result property="region" column="region"/>
|
||||
<result property="latitude" column="latitude"/>
|
||||
<result property="longitude" column="longitude"/>
|
||||
<result property="cuisineType" column="cuisine_type"/>
|
||||
<result property="priceRange" column="price_range"/>
|
||||
<result property="phone" column="phone"/>
|
||||
<result property="website" column="website"/>
|
||||
<result property="googlePlaceId" column="google_place_id"/>
|
||||
<result property="businessStatus" column="business_status"/>
|
||||
<result property="rating" column="rating"/>
|
||||
<result property="ratingCount" column="rating_count"/>
|
||||
<result property="updatedAt" column="updated_at"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- ===== Queries ===== -->
|
||||
|
||||
<select id="findAll" resultMap="restaurantMap">
|
||||
SELECT DISTINCT r.id, r.name, r.address, r.region, r.latitude, r.longitude,
|
||||
r.cuisine_type, r.price_range, r.google_place_id,
|
||||
r.business_status, r.rating, r.rating_count, r.updated_at
|
||||
FROM restaurants r
|
||||
<if test="channel != null and channel != ''">
|
||||
JOIN video_restaurants vr_f ON vr_f.restaurant_id = r.id
|
||||
JOIN videos v_f ON v_f.id = vr_f.video_id
|
||||
JOIN channels c_f ON c_f.id = v_f.channel_id
|
||||
</if>
|
||||
<where>
|
||||
r.latitude IS NOT NULL
|
||||
AND EXISTS (SELECT 1 FROM video_restaurants vr0 WHERE vr0.restaurant_id = r.id)
|
||||
<if test="cuisine != null and cuisine != ''">
|
||||
AND r.cuisine_type = #{cuisine}
|
||||
</if>
|
||||
<if test="region != null and region != ''">
|
||||
AND r.region LIKE '%' || #{region} || '%'
|
||||
</if>
|
||||
<if test="channel != null and channel != ''">
|
||||
AND c_f.channel_name = #{channel}
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY r.updated_at DESC
|
||||
OFFSET #{offset} ROWS FETCH NEXT #{limit} ROWS ONLY
|
||||
</select>
|
||||
|
||||
<select id="findById" resultMap="restaurantMap">
|
||||
SELECT r.id, r.name, r.address, r.region, r.latitude, r.longitude,
|
||||
r.cuisine_type, r.price_range, r.phone, r.website, r.google_place_id,
|
||||
r.business_status, r.rating, r.rating_count
|
||||
FROM restaurants r
|
||||
WHERE r.id = #{id}
|
||||
</select>
|
||||
|
||||
<select id="findVideoLinks" resultType="map">
|
||||
SELECT v.video_id, v.title, v.url, v.published_at,
|
||||
vr.foods_mentioned, vr.evaluation, vr.guests,
|
||||
c.channel_name, c.channel_id
|
||||
FROM video_restaurants vr
|
||||
JOIN videos v ON v.id = vr.video_id
|
||||
JOIN channels c ON c.id = v.channel_id
|
||||
WHERE vr.restaurant_id = #{restaurantId}
|
||||
ORDER BY v.published_at DESC
|
||||
</select>
|
||||
|
||||
<!-- ===== Insert ===== -->
|
||||
|
||||
<insert id="insertRestaurant">
|
||||
INSERT INTO restaurants (id, name, address, region, latitude, longitude,
|
||||
cuisine_type, price_range, google_place_id,
|
||||
phone, website, business_status, rating, rating_count)
|
||||
VALUES (#{id}, #{name}, #{address}, #{region}, #{latitude}, #{longitude},
|
||||
#{cuisineType}, #{priceRange}, #{googlePlaceId},
|
||||
#{phone}, #{website}, #{businessStatus}, #{rating}, #{ratingCount})
|
||||
</insert>
|
||||
|
||||
<!-- ===== Update with COALESCE ===== -->
|
||||
|
||||
<update id="updateRestaurant">
|
||||
UPDATE restaurants SET
|
||||
name = #{name},
|
||||
address = COALESCE(#{address}, address),
|
||||
region = COALESCE(#{region}, region),
|
||||
latitude = COALESCE(#{latitude}, latitude),
|
||||
longitude = COALESCE(#{longitude}, longitude),
|
||||
cuisine_type = COALESCE(#{cuisineType}, cuisine_type),
|
||||
price_range = COALESCE(#{priceRange}, price_range),
|
||||
google_place_id = COALESCE(#{googlePlaceId}, google_place_id),
|
||||
phone = COALESCE(#{phone}, phone),
|
||||
website = COALESCE(#{website}, website),
|
||||
business_status = COALESCE(#{businessStatus}, business_status),
|
||||
rating = COALESCE(#{rating}, rating),
|
||||
rating_count = COALESCE(#{ratingCount}, rating_count),
|
||||
updated_at = SYSTIMESTAMP
|
||||
WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<!-- ===== Dynamic field update ===== -->
|
||||
|
||||
<update id="updateFields">
|
||||
UPDATE restaurants SET
|
||||
<trim suffixOverrides=",">
|
||||
<if test="fields.containsKey('name')">
|
||||
name = #{fields.name},
|
||||
</if>
|
||||
<if test="fields.containsKey('address')">
|
||||
address = #{fields.address},
|
||||
</if>
|
||||
<if test="fields.containsKey('region')">
|
||||
region = #{fields.region},
|
||||
</if>
|
||||
<if test="fields.containsKey('cuisine_type')">
|
||||
cuisine_type = #{fields.cuisine_type},
|
||||
</if>
|
||||
<if test="fields.containsKey('price_range')">
|
||||
price_range = #{fields.price_range},
|
||||
</if>
|
||||
<if test="fields.containsKey('phone')">
|
||||
phone = #{fields.phone},
|
||||
</if>
|
||||
<if test="fields.containsKey('website')">
|
||||
website = #{fields.website},
|
||||
</if>
|
||||
<if test="fields.containsKey('latitude')">
|
||||
latitude = #{fields.latitude},
|
||||
</if>
|
||||
<if test="fields.containsKey('longitude')">
|
||||
longitude = #{fields.longitude},
|
||||
</if>
|
||||
updated_at = SYSTIMESTAMP,
|
||||
</trim>
|
||||
WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<!-- ===== Cascade deletes ===== -->
|
||||
|
||||
<delete id="deleteVectors">
|
||||
DELETE FROM restaurant_vectors WHERE restaurant_id = #{id}
|
||||
</delete>
|
||||
|
||||
<delete id="deleteReviews">
|
||||
DELETE FROM user_reviews WHERE restaurant_id = #{id}
|
||||
</delete>
|
||||
|
||||
<delete id="deleteFavorites">
|
||||
DELETE FROM user_favorites WHERE restaurant_id = #{id}
|
||||
</delete>
|
||||
|
||||
<delete id="deleteVideoRestaurants">
|
||||
DELETE FROM video_restaurants WHERE restaurant_id = #{id}
|
||||
</delete>
|
||||
|
||||
<delete id="deleteRestaurant">
|
||||
DELETE FROM restaurants WHERE id = #{id}
|
||||
</delete>
|
||||
|
||||
<!-- ===== Link video-restaurant ===== -->
|
||||
|
||||
<insert id="linkVideoRestaurant">
|
||||
INSERT INTO video_restaurants (id, video_id, restaurant_id, foods_mentioned, evaluation, guests)
|
||||
VALUES (#{id}, #{videoId}, #{restaurantId}, #{foods}, #{evaluation}, #{guests})
|
||||
</insert>
|
||||
|
||||
<!-- ===== Lookups ===== -->
|
||||
|
||||
<select id="findIdByPlaceId" resultType="string">
|
||||
SELECT id FROM restaurants WHERE google_place_id = #{placeId} FETCH FIRST 1 ROWS ONLY
|
||||
</select>
|
||||
|
||||
<select id="findIdByName" resultType="string">
|
||||
SELECT id FROM restaurants WHERE name = #{name} FETCH FIRST 1 ROWS ONLY
|
||||
</select>
|
||||
|
||||
<!-- ===== Batch enrichment queries ===== -->
|
||||
|
||||
<select id="findChannelsByRestaurantIds" resultType="map">
|
||||
SELECT DISTINCT vr.restaurant_id, c.channel_name
|
||||
FROM video_restaurants vr
|
||||
JOIN videos v ON v.id = vr.video_id
|
||||
JOIN channels c ON c.id = v.channel_id
|
||||
WHERE vr.restaurant_id IN
|
||||
<foreach item="id" collection="ids" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
</select>
|
||||
|
||||
<select id="findFoodsByRestaurantIds" resultType="map">
|
||||
SELECT vr.restaurant_id, vr.foods_mentioned
|
||||
FROM video_restaurants vr
|
||||
WHERE vr.restaurant_id IN
|
||||
<foreach item="id" collection="ids" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
</select>
|
||||
|
||||
<!-- ===== Remap operations ===== -->
|
||||
|
||||
<update id="updateCuisineType">
|
||||
UPDATE restaurants SET cuisine_type = #{cuisineType} WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<update id="updateFoodsMentioned">
|
||||
UPDATE video_restaurants SET foods_mentioned = #{foods} WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<select id="findForRemapCuisine" resultType="map">
|
||||
SELECT r.id, r.name, r.cuisine_type,
|
||||
(SELECT LISTAGG(vr.foods_mentioned, '|') WITHIN GROUP (ORDER BY vr.id)
|
||||
FROM video_restaurants vr WHERE vr.restaurant_id = r.id) AS foods
|
||||
FROM restaurants r
|
||||
WHERE EXISTS (SELECT 1 FROM video_restaurants vr2 WHERE vr2.restaurant_id = r.id)
|
||||
ORDER BY r.name
|
||||
</select>
|
||||
|
||||
<select id="findForRemapFoods" resultType="map">
|
||||
SELECT vr.id, r.name, r.cuisine_type, vr.foods_mentioned, v.title
|
||||
FROM video_restaurants vr
|
||||
JOIN restaurants r ON r.id = vr.restaurant_id
|
||||
JOIN videos v ON v.id = vr.video_id
|
||||
ORDER BY r.name
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user