Object cache and query cache are independent from each other, though they can affect each other's state.
Object Cache
Object cache is accessed when expanding object graph (as you correctly noticed). But synchronization of object cache does not just update the cache, but is also propagated to ObjectContexts, causing a refresh of the in-memory object graph. While this sounds awesome and super-automatic, from experience it is most useful in desktop apps, when you have a limited graph of objects relevant for a single user. In clustered multi-user web applications syncing object cache is more of a nuisance then help. It creates lots of network traffic, forces your instances to consume CPU to process events they don't really care about, and finally, updates your objects from underneath when you least expect it. So I usually turn off object cache syncing, and rely purely on clustered query cache to handle synchronization.
Query Cache
Here is an example project demonstrating query cache with clustering. It uses Cayenne 4.0, EHCache as a cache provider, and ActiveMQ/JMS for cross-instance events.
The way query cache works, it caches lists of objects that matched a given query, including prefetched related objects if the query contained prefetches. To take full advantage of query cache, you may have to change your coding style a bit. Instead of storing long-lived references to query result lists in your instance variables (essentially doing your own caching), you create and run a query (with proper cache settings) any time you need such list, and let Cayenne decide whether the list should be returned from cache or fetched fresh from DB.
The next step is to configure your cache in a way depending on what provider you are using to specify its default expiration policies (per cache group). E.g. a sample EHCache config.
Finally you can add clustering and event-driven cache refreshing, which can be done either explicitly via API calls, or implicitly on commit of certain entities (only available since Cayenne 3.1).
Query cache refreshing is very cheap. The only thing sent across the network is the name of the "cache group", and on the receiving end a bunch of lists are lazily invalidated at once. It also results in cleaner code.
If you are to rely primarily on the query cache, you don't need to "turn off" object cache per se. Object cache is an integral part of Cayenne and is needed for a number of operations (updates, relationships handling). It will be automatically updated as you run the queries. What you'd normally need to do though, is turn off automated object refreshing. By not enabling object cache clustering, you already avoid cross-JVM sync. Additionally you may use this advice to disable cross-ObjectContext sync within the same VM.
Version of Cayenne
I strongly recommend upgrading to at least Cayenne 3.1 (or even to 4.0.M2). Among other nice things there, the caching mechanism is more mature than 3.0. It will make your experience configuring Cayenne and integrating external cache providers so much easier.